З точки зору дизайну це часто корисно, щоб можна було позначити речі незмінними. Так само, як 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для декларацій функцій із зовнішнім зв’язком (навіть якщо цього додатково не вистачає інших кваліфікаторів зв'язку).