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

   | 

   | 

 Обновлено 10 Окт 2018  | 

 380

Для решения определенных проблем в наследовании, в 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 (4 оценок, среднее: 5,00 из 5)
Загрузка...
Подписаться на обновления:

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

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

ВОЛШЕБНАЯ ТАБЛЕТКА ПО С++