Урок №172. Модифікатори override і final

  Юрій  | 

  Оновл. 23 Бер 2021  | 

 112

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

Залишити відповідь

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