С точки зрения дизайна часто бывает полезно пометить вещи как неизменные. Таким же образом 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
это полезно с точки зрения дизайна / архитектуры.
Это также полезно с точки зрения оптимизатора, так как он предоставляет оптимизатору эту информацию о конструкции, которая позволяет ему девиртуализировать вызовы виртуальных функций (устраняя издержки динамической диспетчеризации и, что еще важнее, устраняя барьер оптимизации между вызывающим и вызываемым абонентами).
Вопрос
Из комментариев:
Почему final и virtual будут использоваться одновременно?
Для базового класса в корне иерархии не имеет смысла объявлять функцию как virtual
и final
. Это кажется мне довольно глупым, поскольку это заставило бы и компилятора, и читателя-человека перепрыгивать ненужные обручи, которых можно избежать, просто избегая virtual
откровенного в таком случае. Однако подклассы наследуют виртуальные функции-члены следующим образом:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
В этом случае, независимо от того, Bar::f
явно ли используется ключевое слово virtual, Bar::f
является виртуальной функцией. В virtual
этом случае ключевое слово становится необязательным. Так что может иметь смысл Bar::f
указывать как final
, даже если это виртуальная функция ( final
может использоваться только для виртуальных функций).
И некоторые люди могут предпочесть, чтобы стилистически явно указать, что Bar::f
является виртуальным, вот так:
struct Bar: Foo
{
virtual void f() final {...}
};
Для меня это несколько избыточно - использовать оба virtual
и final
спецификаторы для одной и той же функции в этом контексте (аналогично virtual
и override
), но в данном случае это вопрос стиля. Некоторые люди могут обнаружить, virtual
что здесь сообщается что-то ценное, во многом как использование extern
для объявлений функций с внешней связью (даже если это необязательно, без других квалификаторов связи).