Какова цель ключевого слова "final" В C++11 для функций?



какова цель final ключевое слово в C++11 функций? Я понимаю, что это предотвращает переопределение функций производными классами, но если это так, то разве недостаточно объявить как невиртуальный ваш final функции? Есть ли еще что-то, что я упускаю здесь?

1234   9  

9 ответов:

что вам не хватает, как idljarn уже упоминалось в комментарии, так это то, что если вы переопределение функция из базового класса, то вы не можете пометить его как невиртуальный:

struct base {
   virtual void f();
};
struct derived : base {
   void f() final;       // virtual as it overrides base::f
};
struct mostderived : derived {
   //void f();           // error: cannot override!
};
  • это для предотвращения наследования класса. От Википедия:

    C++11 также добавляет возможность предотвращения наследования от классов или просто предотвращения переопределения методов в производных классах. Это делается с опциональной. Например:

    struct Base1 final { };
    
    struct Derived1 : Base1 { }; // ill-formed because the class Base1 
                                 // has been marked final
    
  • он также используется для обозначения виртуальной функции, чтобы предотвратить ее переопределение в производном классы:

    struct Base2 {
        virtual void f() final;
    };
    
    struct Derived2 : Base2 {
        void f(); // ill-formed because the virtual function Base2::f has 
                  // been marked final
    };
    

Википедия далее делает интересный момент:

обратите внимание, что ни override, ни final языка ключевые слова. Они являются технически идентификаторами;они приобретают особое значение только при использовании в этих конкретных контекстах. в любом другом месте, они могут быть допустимыми идентификаторами.

это означает, что следующее разрешено:

int const final = 0;     // ok
int const override = 1;  // ok

"final" также позволяет оптимизации компилятора обойти косвенный вызов:

class IAbstract
{
public:
  virtual void DoSomething() = 0;
};

class CDerived : public IAbstract
{
  void DoSomething() final { m_x = 1 ; }

  void Blah( void ) { DoSomething(); }

};

С помощью" final " компилятор может вызвать CDerived::DoSomething() С Blah(), или даже встроенные. Без него он должен генерировать косвенный вызов внутри Blah(), потому что Blah() может быть вызван внутри производного класса, который переопределил DoSomething().

нечего добавить к семантическим аспектам "финала".

но я хотел бы добавить к комментарию Криса Грина, что" финал " может стать очень важным метод оптимизации компилятора в не столь отдаленном будущем. Не только в простом случае, о котором он упомянул, но и для более сложных иерархий реальных классов, которые могут быть "закрыты" "final", что позволяет компиляторам генерировать более эффективный код диспетчеризации, чем при обычном подходе vtable.

одним из ключевых недостатков vtables является то, что для любого такого виртуального объекта (предполагая 64-бит на типичном процессоре Intel) один указатель съедает 25% (8 из 64 байт) строки кэша. В тех приложениях, которые мне нравится писать, это очень больно. (И по моему опыту это аргумент №1 против C++ с точки зрения производительности пуристов, т. е. программистов C.)

в приложениях, которые требуют экстремальной производительности, что не так уж необычно для C++, это может действительно стать удивительным, не требуя, чтобы обойти эту проблему вручную в стиле C или странный шаблон жонглирования.

этот метод известен как Реал. Этот термин стоит запомнить. : -)

есть отличная недавняя речь Андрея Александреску, которая довольно хорошо объясняет, как вы можете обойти такие ситуации сегодня и как "финал" может быть частью решения подобных случаев "автоматически" в будущем (обсуждается с слушатели):

http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly

Final не может быть применен к невиртуальным функциям.

error: only virtual member functions can be marked 'final'

было бы не очень важно, чтобы иметь возможность отметить невиртуальный метод как "окончательный". Учитывая

struct A { void foo(); };
struct B : public A { void foo(); };
A * a = new B;
a -> foo(); // this will call A :: foo anyway, regardless of whether there is a B::foo

a->foo() всегда будет вызывать A::foo.

но, если A:: foo был virtual, тогда B:: foo переопределит его. Это может быть нежелательно, и поэтому имеет смысл сделать виртуальную функцию окончательной.

вопрос в том, хотя,почему разрешить финал на виртуальные функции. Если у вас есть глубокая иерархия:

struct A            { virtual void foo(); };
struct B : public A { virtual void foo(); };
struct C : public B { virtual void foo() final; };
struct D : public C { /* cannot override foo */ };

тут final ставит 'пол' на сколько переопределение может быть сделано. Другие классы могут расширять A и B и переопределять их foo, но это класс расширяет C, то это не допускается.

так что, вероятно, не имеет смысла делать "верхний уровень" foo final, но это может иметь смысл опустить вниз.

(Я думаю, однако, есть место, чтобы расширить слова final и переопределить для невиртуальных членов. Они бы есть другое значение, хотя.)

вариант использования для ключевого слова' final', который мне нравится, выглядит следующим образом:

// This pure abstract interface creates a way
// for unit test suites to stub-out Foo objects
class FooInterface
{
public:
   virtual void DoSomething() = 0;
private:
   virtual void DoSomethingImpl() = 0;
};

// Implement Non-Virtual Interface Pattern in FooBase using final
// (Alternatively implement the Template Pattern in FooBase using final)
class FooBase : public FooInterface
{
public:
    virtual void DoSomething() final { DoFirst(); DoSomethingImpl(); DoLast(); }
private:
    virtual void DoSomethingImpl() { /* left for derived classes to customize */ }
    void DoFirst(); // no derived customization allowed here
    void DoLast(); // no derived customization allowed here either
};

// Feel secure knowing that unit test suites can stub you out at the FooInterface level
// if necessary
// Feel doubly secure knowing that your children cannot violate your Template Pattern
// When DoSomething is called from a FooBase * you know without a doubt that
// DoFirst will execute before DoSomethingImpl, and DoLast will execute after.
class FooDerived : public FooBase
{
private:
    virtual void DoSomethingImpl() {/* customize DoSomething at this location */}
};

final добавляет явное намерение не переопределять вашу функцию и вызовет ошибку компилятора, если это будет нарушено:

struct A {
    virtual int foo(); // #1
};
struct B : A {
    int foo();
};

как код стоит, он компилируется, и B::foo переопределяет A::foo. B::foo тоже виртуальный, кстати. Однако, если мы изменим #1 на virtual int foo() final, то это ошибка компилятора, и мы не можем переопределить A::foo дальше в производных классах.

обратите внимание, что это не позволяет нам "открыть" новую иерархию, т. е. нет никакого способа, чтобы сделать B::foo новая, несвязанная функция, которая может быть независимо во главе новой виртуальной иерархии. После того, как функция является окончательной, она никогда не может быть объявлена снова в любом производном классе.

ключевое слово final позволяет объявить виртуальный метод, переопределить его N раз, а затем указать, что "это больше не может быть переопределено". Было бы полезно ограничить использование вашего производного класса, чтобы вы могли сказать: "я знаю, что мой суперкласс позволяет вам переопределить это, но если вы хотите получить от меня, вы не можете!".

struct Foo
{
   virtual void DoStuff();
}

struct Bar : public Foo
{
   void DoStuff() final;
}

struct Babar : public Bar
{
   void DoStuff(); // error!
}

как указывали другие плакаты, он не может быть применен к невиртуальным функциям.

одной из целей последнего ключевого слова является предотвращение случайное переопределение метода. В моем примере DoStuff () может быть вспомогательной функцией, которую производный класс просто должен переименовать, чтобы получить правильное поведение. Без финала ошибка не будет обнаружена до тестирования.

Ключевое слово Final в C++ при добавлении в функцию предотвращает ее переопределение базовым классом. Также при добавлении в класс предотвращает наследование любого типа. Рассмотрим следующий пример, который показывает использование конечного спецификатора. Эта программа терпит неудачу в компиляции.

#include <iostream>
using namespace std;

class Base
{
  public:
  virtual void myfun() final
  {
    cout << "myfun() in Base";
  }
};
class Derived : public Base
{
  void myfun()
  {
    cout << "myfun() in Derived\n";
  }
};

int main()
{
  Derived d;
  Base &b = d;
  b.myfun();
  return 0;
}

также:

#include <iostream>
class Base final
{
};

class Derived : public Base
{
};

int main()
{
  Derived d;
  return 0;
}

Comments

    Ничего не найдено.