Урок №164. Модификаторы override и final. Ковариантный тип возврата

  Юрий  | 

  Обновл. 27 Июн 2019  | 

 7202

 ǀ   4 

Для решения определённых проблем в наследовании в C++11 добавили два специальных идентификатора: override и final. Обратите внимание, эти идентификаторы (или ещё «модификаторы») не являются ключевыми словами — это обычные идентификаторы, которые имеют особое значение в определённых контекстах.

Хотя final используется не часто, override же является фантастическим дополнением, которое вы должны использовать регулярно. В этом уроке мы рассмотрим оба этих модификатора, а также одно исключение из правил, когда тип возврата переопределения может не совпадать с типом возврата виртуальной функции родительского класса.

Модификатор override

Как мы уже знаем из предыдущего урока, виртуальная функция дочернего класса является переопределением, только если совпадают её сигнатура и тип возврата с сигнатурой и типом возврата виртуальной функции родительского класса. А это, в свою очередь, может привести к проблемам, когда функция, которая должна быть переопределением, на самом деле, им не является.

Рассмотрим следующий пример:

Поскольку rParent — это ссылка класса A на объект b, то с помощью виртуальных функций мы намереваемся получить доступ к B::getName1() и B::getName2(). Однако, поскольку в B::getName1() другой тип параметра (short int вместо int), то он не является переопределением метода A::getName1(). Более того, поскольку B::getName2() является const, а A::getName2() нет, то B::getName2() также не считается переопределением A::getName2().

Следовательно, результат выполнения программы выше:

A
A

Конкретно в этом случае, поскольку A и B просто выводят свои имена, довольно легко увидеть, что что-то пошло не так и переопределения не вызываются. Однако, в более сложной программе, когда методы могут и не возвращать значения, которые выводятся на экран, найти ошибку уже будет довольно проблематично.

Для решения такого типа проблем и добавили модификатор override в C++11. Модификатор override может использоваться с любым методом, который должен быть переопределением. Достаточно просто указать override в том месте, где обычно указывается const (после скобок с параметрами). Если метод не переопределяет виртуальную функцию родительского класса, то компилятор выдаст ошибку:

Здесь мы получим две ошибки: первая для B::getName1() и вторая для B::getName2(), так как ни один из этих методов не является переопределением виртуальных функций класса А. B::getName3() является переопределением, поэтому с ним никаких проблем нет.

Использование модификатора override никак не влияет на эффективность или производительность программы, но помогает избежать непреднамеренных ошибок. Следовательно, настоятельно рекомендуется использовать модификатор override для каждого из своих переопределений.

Правило: Используйте модификатор override для каждого из своих переопределений.

Модификатор final


Могут быть случаи, когда вы не хотите, чтобы кто-то мог переопределить виртуальную функцию или наследовать определённый класс. Модификатор final используется именно для этого. Если пользователь пытается переопределить метод или наследовать класс с модификатором final, то компилятор выдаст ошибку.

Указывается final в том же месте, где и модификатор override. Например:

В коде выше B::getName() переопределяет A::getName(). Но B::getName() имеет спецификатор final, что означает, что любые дальнейшие переопределения этого метода будут вызывать ошибку компиляции. И действительно, C::getName() уже не может переопределить B::getName() — компилятор выдаст ошибку.

В случае, если мы хотим запретить наследование определённого класса, то модификатор final указывается после имени класса:

В примере выше класс B объявлен как final. Таким образом, класс C не может наследовать класс B — компилятор выдаст ошибку.

Ковариантный тип возврата

Есть один случай, когда тип возврата переопределения может не совпадать с типом возврата виртуальной функции родительского класса, но при этом оставаться переопределением. Если типом возврата виртуальной функции является указатель или ссылка на класс, то переопределения могут возвращать указатель или ссылку на свой собственный класс (т.е. вместо родительского класса указывать на дочерний класс). Это называется ковариантным типом возврата. Например:

Результат выполнения программы выше:

called Child::getThis()
returned a Child
called Child::getThis()
returned a Parent

Некоторые старые компиляторы могут не поддерживать ковариантные типы возврата.

В примере выше мы сначала вызываем ch.getThis(). Поскольку ch является объектом класса Child, то вызывается Child::getThis(), который возвращает Child*. Этот Child* затем используется для вызова не виртуальной функции Child::printType().

Затем выполняется p->getThis(). Переменная p является указателем класса Parent на объект ch класса Child. Parent::getThis() — это виртуальная функция, поэтому вызывается переопределение Child::getThis(). Хотя Child::getThis() и возвращает Child*, но, поскольку родительская часть объекта возвращает Parent*, то возвращаемый Child* преобразовывается в Parent*. И, таким образом, вызывается Parent::printType().

Другими словами, в примере выше мы получим Child* только в том случае, если будем вызывать getThis() с объектом класса Child.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (42 оценок, среднее: 4,81 из 5)
Загрузка...

Комментариев: 4

  1. Аватар Анастасия:

    Как-то не зашёл данный урок.

    1) Зачем, если мы не хотим делать дочерний метод переопределяющим, ставить final, ведь достаточно в этом случае не указывать virtual? Или это не поможет, т.к. virtual достаточно пометить лишь родительский метод, и дочерний сам того не желая станет переопределяющим?

    2) Это вообще было трудно:

    методы у нас же для объектов, так? А р является указателем (хоть и на объект), что-то у меня "метод указателя" никак в голове не укладывается. Ну и в принципе выкладка не очевидная, я представить себе не могу чтобы такое где-то повторить.

    1. Аватар Denis:

      1) Да, если Вы указываете родительский метод как virtual, то методы в дочерних классах станут переопределяющим автоматичеки (хотя для хорошей читаемости и в дочерних классах ставят virtual)
      2) Это написано для удобочитаемости, чтобы явно не разыминовывать указатель. Если написать (*(*p).getThis()).printType(), то будет это выглядеть мягко говоря не очень)

  2. Аватар Оксана:

    Да, кстати, проверила последний пример. На моем компиляторе получилось

    this is Child
    this is Child getThis
    this is Child
    this is Child getThis

    То есть указатель на Child к Parent не приводится в строке:

  3. Аватар Александр:

    Хотя Child::getThis() и возвращает Child*, но, поскольку родительская часть объекта возвращает Parent*, то возвращаемый Child* преобразовывается в Parent*.

    Можно здесь более подробно? Речь идёт об объекте ch? Почему тогда в первом случае (ch.getThis()->printType();) родительская часть не возвращает *Parent?

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *

telegram канал
НОВОСТИ RAVESLI