Какова цель final
ключевого слова в C ++ 11 для функций? Я понимаю, что это предотвращает переопределение функций производными классами, но если это так, то разве недостаточно объявить ваши final
функции не виртуальными ? Есть еще кое-что, что мне здесь не хватает?
146
virtual
ключевое слово или нет.func
не виртуальный, поэтому нечего переопределять и, следовательно, нечего отмечать какoverride
илиfinal
.Ответы:
Что вам не хватает, поскольку 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! };
источник
virtual
может вызвать ошибки, и C ++ 11 добавилoverride
тег к функции, которая обнаружит эту ситуацию и не сможет скомпилировать, когда функция, предназначенная для переопределения, на самом деле скрываетсяЭто необходимо для предотвращения наследования класса. Из Википедии :
Он также используется для обозначения виртуальной функции, чтобы предотвратить ее переопределение в производных классах:
Википедия также отмечает интересный момент :
Это означает, что разрешено следующее:
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" может стать очень важным методом оптимизации компилятора в не столь отдаленном будущем. Не только в упомянутом им простом случае, но также и для более сложных иерархий реальных классов, которые могут быть «закрыты» с помощью «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 переопределил бы его. Это может быть нежелательно, и, следовательно, имеет смысл сделать виртуальную функцию окончательной.Однако возникает вопрос, зачем разрешать final для виртуальных функций. Если у вас глубокая иерархия:
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 и override на невиртуальные члены. Однако они будут иметь другое значение.)
источник
final
. Например, если вы знаете, что хотите, чтобы всеShape
s были чем-foo()
то предопределенным и определенным, что никакая производная форма не должна изменяться. Или я ошибаюсь, и есть лучший образец для этого случая? РЕДАКТИРОВАТЬ: О, может быть, потому что в этом случае просто не следует делать верхний уровеньfoo()
virtual
для начала? Но, тем не менее, его можно скрыть, даже если он называется правильно (полиморфно) черезShape*
...Пример использования ключевого слова 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
новую, несвязанную функцию, которая могла бы независимо стоять во главе новой виртуальной иерархии. Как только функция является окончательной, ее больше нельзя объявить ни в каком производном классе.источник
Последнее ключевое слово позволяет вам объявить виртуальный метод, переопределить его N раз, а затем потребовать, чтобы «это больше нельзя было переопределить». Было бы полезно ограничить использование вашего производного класса, чтобы вы могли сказать: «Я знаю, что мой суперкласс позволяет вам переопределить это, но если вы хотите наследовать от меня, вы не можете!».
struct Foo { virtual void DoStuff(); } struct Bar : public Foo { void DoStuff() final; } struct Babar : public Bar { void DoStuff(); // error! }
Как указывалось на других плакатах, его нельзя применять к невиртуальным функциям.
Одна из целей ключевого слова final - предотвратить случайное переопределение метода. В моем примере DoStuff () могла быть вспомогательной функцией, которую производный класс просто нужно переименовать, чтобы получить правильное поведение. Без final ошибка не будет обнаружена до тестирования.
источник
Ключевое слово Final в C ++ при добавлении к функции предотвращает его переопределение базовым классом. Также при добавлении в класс предотвращает наследование любого типа. Рассмотрим следующий пример, который показывает использование спецификатора final. Эта программа не компилируется.
#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; }
источник
Дополнение к ответу Марио Кнезовича:
class IA { public: virtual int getNum() const = 0; }; class BaseA : public IA { public: inline virtual int getNum() const final {return ...}; }; class ImplA : public BaseA {...}; IA* pa = ...; ... ImplA* impla = static_cast<ImplA*>(pa); //the following line should cause compiler to use the inlined function BaseA::getNum(), //instead of dynamic binding (via vtable or something). //any class/subclass of BaseA will benefit from it int n = impla->getNum();
Приведенный выше код демонстрирует теорию, но не тестировался на реальных компиляторах. Будем признательны, если кто-нибудь вставит разобранный вывод.
источник