Какова цель ключевого слова "final" в С++ 11 для функций?
Какова цель ключевого слова final
в С++ 11 для функций? Я понимаю, что это предотвращает переопределение функций производными классами, но если это так, то разве это недостаточно, чтобы объявить как не виртуальные ваши функции final
? Есть ли еще одна вещь, которую я здесь отсутствует?
Ответы
Ответ 1
Что вам не хватает, поскольку 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!
};
Ответ 2
-
Это значит, что класс не будет унаследован. Из Wikipedia:
С++ 11 также добавляет возможность предотвращения наследования от классов или просто предотвращения переопределения методов в производных классах. Это делается с помощью специального идентификатора final. Например:
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
не являются языковыми ключевыми словами. Они являются техническими идентификаторами; они приобретают особый смысл при использовании в этих конкретных контекстах. В любом другом месте они могут быть действительными идентификаторами.
Ответ 3
"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()
.
Ответ 4
Ничего не добавить к семантическим аспектам "final".
Но я хотел бы добавить к chris green комментарий, что "final" может стать очень важным инструментом оптимизации компилятора в недалеком будущем. Не только в простом случае, о котором он упомянул, но и о более сложных иерархиях классов реального мира, которые могут быть "закрыты" "окончательным", что позволяет компиляторам генерировать более эффективный код диспетчеризации, чем с обычным подходом vtable.
Одним из основных недостатков vtables является то, что для любого такого виртуального объекта (предполагающего 64-разрядные бит на типичном процессоре Intel) только один указатель потребляет 25% (8 из 64 байтов) строки кэша. В таких приложениях мне нравится писать, это очень больно. (И по моему опыту это аргумент №1 против С++ с точки зрения производительности пуриста, то есть программистами C.)
В приложениях, требующих экстремальной производительности, что не так уж и необычно для С++, это действительно может стать ужасным, не требуя, чтобы эта проблема была решена вручную в стиле C или странном жонглировании шаблонов.
Этот метод известен как Devirtualization. Термин стоит вспомнить.: -)
Существует замечательная недавняя речь Андрея Александреску, в которой довольно подробно объясняется, как вы можете сегодня обходиться с такими ситуациями и как "окончательный" может быть частью решения подобных случаев "автоматически" в будущем (обсуждается с слушателями):
http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly
Ответ 5
Финал нельзя применять к не виртуальным функциям.
error: only virtual member functions can be marked 'final'
Было бы нецелесообразно отмечать не виртуальный метод как "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 и переопределить их для не виртуальных участников. Они будут иметь другое значение.)
Ответ 6
Пример использования ключевого слова "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 */}
};
Ответ 7
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
новую, несвязанную функцию, которая может быть независимо во главе новой виртуальной иерархии. Как только функция является окончательной, она никогда не может быть объявлена повторно в любом производном классе.
Ответ 8
Последнее ключевое слово позволяет объявить виртуальный метод, переопределить его N раз, а затем указать, что "это больше нельзя переопределить". Было бы полезно ограничить использование вашего производного класса, чтобы вы могли сказать: "Я знаю, что мой суперкласс позволяет вам переопределить это, но если вы хотите получить от меня, вы не можете!".
struct Foo
{
virtual void DoStuff();
}
struct Bar : public Foo
{
void DoStuff() final;
}
struct Babar : public Bar
{
void DoStuff(); // error!
}
Как отмечали другие плакаты, он не может применяться к не виртуальным функциям.
Одна цель последнего ключевого слова - предотвратить случайное переопределение метода. В моем примере DoStuff(), возможно, была вспомогательной функцией, которую производный класс просто нужно переименовать, чтобы получить правильное поведение. Без окончательной ошибки ошибка не будет обнаружена до тестирования.
Ответ 9
Конечное ключевое слово в С++ при добавлении к функции предотвращает его переопределение базовым классом.
Также при добавлении в класс предотвращает наследование любого типа.
Рассмотрим следующий пример, который показывает использование конечного спецификатора. Эта компиляция не выполняется.
#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;
}