З точки зору дизайну це часто корисно, щоб можна було позначити речі незмінними. Так само, як const
забезпечує захист компілятора і вказує, що стан не повинен змінюватися, final
можна використовувати для вказівки на те, що поведінка не повинна змінюватися далі вниз по ієрархії спадкування.
Приклад
Розглянемо відео-гру, де транспортні засоби переносять програвача з одного місця в інше. Усі транспортні засоби повинні перевірити, щоб переконатися, що вони їдуть до дійсного місця до вильоту (переконайтесь, що база в цьому місці не зруйнована, наприклад). Ми можемо почати використовувати ідіому невіртуального інтерфейсу (NVI), щоб гарантувати, що ця перевірка зроблена незалежно від транспортного засобу.
class Vehicle
{
public:
virtual ~Vehicle {}
bool transport(const Location& location)
{
// Mandatory check performed for all vehicle types. We could potentially
// throw or assert here instead of returning true/false depending on the
// exceptional level of the behavior (whether it is a truly exceptional
// control flow resulting from external input errors or whether it's
// simply a bug for the assert approach).
if (valid_location(location))
return travel_to(location);
// If the location is not valid, no vehicle type can go there.
return false;
}
private:
// Overridden by vehicle types. Note that private access here
// does not prevent derived, nonfriends from being able to override
// this function.
virtual bool travel_to(const Location& location) = 0;
};
Тепер скажімо, що у нашій грі є літальні апарати, і те, що вимагають і мають спільне всі літаючі транспортні засоби, - це те, що вони повинні пройти перевірку безпеки всередині ангару перед зльотом.
Тут ми можемо використовувати, final
щоб гарантувати, що всі літаючі транспортні засоби пройдуть таку перевірку, а також повідомити цю вимогу щодо дизайну літальних апаратів.
class FlyingVehicle: public Vehicle
{
private:
bool travel_to(const Location& location) final
{
// Mandatory check performed for all flying vehicle types.
if (safety_inspection())
return fly_to(location);
// If the safety inspection fails for a flying vehicle,
// it will not be allowed to fly to the location.
return false;
}
// Overridden by flying vehicle types.
virtual void safety_inspection() const = 0;
virtual void fly_to(const Location& location) = 0;
};
Використовуючи final
таким чином, ми ефективно розширюємо гнучкість ідіоми невіртуального інтерфейсу, щоб забезпечити рівномірну поведінку вниз по ієрархії спадкування (навіть як задум, протидіючи крихкій проблемі базового класу) до самих віртуальних функцій. Крім того, ми купуємо собі кімнату, коли ми маємо змогу вносити центральні зміни, які впливають на всі типи літаючих транспортних засобів як заздалегідь, не змінюючи кожну існуючу реалізацію літального апарату.
Це один із таких прикладів використання final
. Ви можете зіткнутися з контекстами, у яких просто не має сенсу перестати відмінювати функцію віртуального члена - це може призвести до крихкого дизайну та порушення ваших вимог до дизайну.
Ось де final
корисно з дизайнерської / архітектурної точки зору.
Це також корисно з точки зору оптимізатора, оскільки він надає оптимізатору цю дизайнерську інформацію, що дозволяє йому девіартуалізувати віртуальні виклики функцій (усуваючи накладні динамічні диспетчеризації, а нерідко і значно значніше, усуваючи бар'єр для оптимізації між абонентом та викликом).
Питання
З коментарів:
Чому остаточне та віртуальне коли-небудь використовуватимуться одночасно?
Немає сенсу базовий клас в корені ієрархії оголошувати функцію як і, virtual
і final
. Мені це здається досить нерозумним, оскільки це би змусило і компілятора, і людського читача перестрибувати непотрібні обручі, чого можна уникнути, просто уникнувши virtual
прямого в такому випадку. Однак підкласи успадковують такі функції віртуальних членів:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
У цьому випадку, незалежно від того, Bar::f
використовує віртуальне ключове слово чи ні , Bar::f
це віртуальна функція. У virtual
цьому випадку ключове слово стає необов’язковим. Тому може бути сенс, Bar::f
щоб його вказали як final
, хоча це віртуальна функція ( final
може використовуватися лише для віртуальних функцій).
І деякі люди можуть віддати перевагу, стилістично, чітко вказати, що Bar::f
це віртуально:
struct Bar: Foo
{
virtual void f() final {...}
};
Мені начебто надмірно використовувати в цьому контексті virtual
і final
специфікатори для тієї ж функції (так само virtual
і override
), але це питання стилю в даному випадку. Деякі люди можуть це знайтиvirtual
передається щось цінне, як, наприклад, використання extern
для декларацій функцій із зовнішнім зв’язком (навіть якщо цього додатково не вистачає інших кваліфікаторів зв'язку).