Я знаю, что final
ключевое слово используется для предотвращения переопределения виртуального метода производными классами. Тем не менее, я не могу найти никакого полезного примера, когда я действительно должен использовать final
ключевое слово с virtual
методом. Более того, такое ощущение, что использование final
виртуальных методов - это неприятный запах, так как это не позволяет программистам расширять класс в будущем.
Мой вопрос следующий:
Есть ли полезный случай, когда я действительно должен использовать final
в virtual
объявлении метода?
Ответы:
Резюме того, что
final
делает ключевое слово: допустим, у нас есть базовый классA
и производный классB
. Функцияf()
может быть объявленаA
какvirtual
, что означает, что классB
может ее переопределить. Но тогда классB
может пожелать, чтобы любой класс, который является производным от него,B
не мог переопределять егоf()
. Вот тогда мы должны объявитьf()
какfinal
вB
. Безfinal
ключевого слова, если мы определим метод как виртуальный, любой производный класс сможет переопределить его.final
Используется ключевое слово , чтобы положить конец этой свободы.Пример того, почему вам может понадобиться такая вещь: предположим, класс
A
определяет виртуальную функциюprepareEnvelope()
, а классB
переопределяет ее и реализует как последовательность вызовов своих собственных виртуальных методовstuffEnvelope()
,lickEnvelope()
иsealEnvelope()
. КлассB
намеревается разрешить производным классам переопределять эти виртуальные методы для предоставления своих собственных реализаций, но классB
не хочет, чтобы какой-либо производный класс переопределялprepareEnvelope()
и, таким образом, изменял порядок вещей, лизал, запечатывал или опускал вызов одной из них. Таким образом, в этом случае классB
объявляетсяprepareEnvelope()
как окончательный.источник
class B might wish that any class which is further derived from B should not be able to override f()
? Есть ли в реальном мире случай, когда такой класс B и метод f () существуют и хорошо подходят?С точки зрения дизайна часто бывает полезно пометить вещи как неизменные. Таким же образом
const
обеспечивает защиту компилятора и указывает, что состояние не должно меняться,final
может использоваться для указания того, что поведение не должно меняться дальше вниз по иерархии наследования.пример
Рассмотрим видеоигру, в которой транспортные средства доставляют игрока из одного места в другое. Все транспортные средства должны проверить, чтобы убедиться, что они едут в правильное место до отправления (например, убедитесь, что база в этом месте не уничтожена). Мы можем начать с использования не-виртуального интерфейса (NVI), чтобы гарантировать, что эта проверка выполняется независимо от транспортного средства.
Теперь предположим, что в нашей игре есть летательные аппараты, и все, что требуется от всех летательных аппаратов, - это то, что перед взлетом они должны пройти проверку безопасности внутри ангара.
Здесь мы можем использовать,
final
чтобы гарантировать, что все летательные аппараты пройдут такую проверку, а также сообщить это требование к конструкции летательных аппаратов.Используя
final
таким образом, мы эффективно своего рода расширение гибкости невиртуальном интерфейса идиомы , чтобы обеспечить единообразное поведение вниз по иерархии наследования (даже задним числом, борьбе с проблемой хрупкого базового класса) виртуальных функций самостоятельно. Кроме того, мы покупаем себе пространство для маневра, чтобы вносить центральные изменения, которые влияют на все типы летательных аппаратов в качестве запоздалой мысли, не изменяя каждую существующую реализацию летательного аппарата.Это один из таких примеров использования
final
. Вы можете столкнуться с контекстом, когда просто не имеет смысла переопределять виртуальную функцию-член - это может привести к хрупкому дизайну и нарушению ваших требований к дизайну.Вот где
final
это полезно с точки зрения дизайна / архитектуры.Это также полезно с точки зрения оптимизатора, так как он предоставляет оптимизатору эту информацию о конструкции, которая позволяет ему девиртуализировать вызовы виртуальных функций (устраняя издержки динамической диспетчеризации и, что еще важнее, устраняя барьер оптимизации между вызывающим и вызываемым абонентами).
Вопрос
Из комментариев:
Для базового класса в корне иерархии не имеет смысла объявлять функцию как
virtual
иfinal
. Это кажется мне довольно глупым, поскольку это заставило бы и компилятора, и читателя-человека перепрыгивать ненужные обручи, которых можно избежать, просто избегаяvirtual
откровенного в таком случае. Однако подклассы наследуют виртуальные функции-члены следующим образом:В этом случае, независимо от того,
Bar::f
явно ли используется ключевое слово virtual,Bar::f
является виртуальной функцией. Вvirtual
этом случае ключевое слово становится необязательным. Так что может иметь смыслBar::f
указывать какfinal
, даже если это виртуальная функция (final
может использоваться только для виртуальных функций).И некоторые люди могут предпочесть, чтобы стилистически явно указать, что
Bar::f
является виртуальным, вот так:Для меня это несколько избыточно - использовать оба
virtual
иfinal
спецификаторы для одной и той же функции в этом контексте (аналогичноvirtual
иoverride
), но в данном случае это вопрос стиля. Некоторые люди могут обнаружить,virtual
что здесь сообщается что-то ценное, во многом как использованиеextern
для объявлений функций с внешней связью (даже если это необязательно, без других квалификаторов связи).источник
private
метод внутри вашегоVehicle
класса? Разве ты не имел в видуprotected
вместо этого?private
не распространяется на переопределение (немного нелогично ). Спецификаторы public / protected / private для виртуальных функций применяются только к вызывающим, а не к переопределителям, грубо говоря. Производный класс может переопределять виртуальные функции из своего базового класса независимо от его видимости.protected
может иметь немного более интуитивный смысл. Я просто предпочитаю достигать минимальной видимости, когда это возможно. Я думаю, что причина того, что язык разработан таким образом, заключается в том, что в противном случае частные виртуальные функции-члены не будут иметь никакого смысла вне контекста дружбы, поскольку ни один класс, кроме друга, не сможет переопределить их, если спецификаторы доступа имеют значение в контексте переопределять, а не просто звонить.protected
чтобы не запутать других. Я закончил тем, что добавил комментарий, по крайней мере, описывающий, как частные виртуальные функции все еще могут быть переопределены.Это позволяет много оптимизаций, потому что во время компиляции может быть известно, какая функция вызывается.
Будьте осторожны, разбрасывая слово «запах кода». «окончательный» не делает невозможным расширение класса. Двойной щелчок на «последнем» слове, удар по пробелу и расширение класса. ОДНАКО final - это отличная документация, в которой разработчик не ожидает, что вы переопределите функцию, и поэтому следующий разработчик должен быть очень осторожным, потому что класс может перестать работать должным образом, если метод final будет переопределен таким образом.
источник
final
иvirtual
когда-либо использоваться в то же время?final
иoverride
.final
может действительно помочь им