Урок №180. Вивід об’єктів класів через оператор виводу

  Юрій  | 

  Оновл. 27 Вер 2021  | 

 148

На цьому уроці ми розглянемо, як виводити об’єкти класів через оператор виводу в мові С++.

Проблема з перевизначенням operator<<

Розглянемо наступну програму:

Тут зрозуміло, що p.print() викликає Child::print() (оскільки p посилається на об’єкт класу Child, то Parent::print() є віртуальною функцією, а Child::print() є перевизначенням).

Такий спосіб виводу непоганий, але з std::cout не дуже добре поєднується:

На цьому уроці ми розглянемо, як перевизначити оператор виводу << для класів з спадкуванням, щоб мати можливість використовувати оператор << наступним чином:

Почнемо зі звичайного перевантаження оператора виводу <<:

Оскільки тут немає віртуальних функцій, то все досить-таки просто і ясно:

Parent
Child

Тепер замінимо функцію main() на наступну:

Результат:

Parent

А це вже не те, що нам потрібно. Оскільки перевантаження оператора << для об’єктів класу Parent не є віртуальним, то std::cout << pref викликає версію оператора <<, яка працює тільки з об’єктами класу Parent. В цьому і суть проблеми.

Чи можемо ми зробити operator<< віртуальним?

Ні, і на це є ряд причин.

По-перше, тільки методи можуть бути віртуальними. Це логічно, тому що тільки класи можуть наслідувати інші класи, і перевизначити функцію, яка знаходиться поза тілом класу — неможливо (ми можемо перевантажити функції, які не є методами, але не можемо перевизначити їх) . Оскільки оператор << зазвичай перевантажується через дружню функцію, а дружні функції не є методами, то дружня функція operator<< не може бути перевизначена.

По-друге, навіть якби ми могли зробити operator<< віртуальною функцією, то проблема полягає в тому, що параметри Parent::operator<< і Child::operator<< відрізняються (версія Parent приймає в якості параметру об’єкт класу Parent, а версія Child — об’єкт класу Child). Отже, версія Child не може вважатися перевизначенням версії Parent і викликатися в якості перевизначення також не може.

Що робити програмісту?

Рішення

Відповідь досить-таки проста.

Спочатку ми робимо operator<< дружньою функцією класу Parent. Але замість того, щоб operator<< виконував вивід самостійно, ми делегуємо цю задачу звичайному методу, який є віртуальною функцією!

Розглянемо це на практиці:

Вищенаведена програма працює у всіх 3 випадках:

Parent
Child
Child

Розглянемо детально.

У випадку з об’єктом класу Parent, ми викликаємо operator<<, який викликає віртуальну функцію print(). Оскільки ми посилаємося на об’єкт класу Parent, то p.print() викликає Parent::print(), який і виконує вивід на екран. Тут все просто.

У випадку з об’єктом класу Child, компілятор спочатку дивиться, чи є operator<<, який приймає об’єкт класу Child. Він нічого не знаходить (тому що ми це не визначили), потім дивиться, чи є operator<<, який приймає об’єкт класу Parent. Є, компілятор знаходить і виконує неявну конвертацію (підвищуюче приведення) об’єкта класу Child (посилання на об’єкт класу Child) в посилання класу Parent і викликає віртуальну функцію print(), яка, в свою чергу, викликає перевизначення Child::print().

Зверніть увагу, нам не потрібно записувати перевантаження operator<< в кожному дочірньому класі! Перевантаження, яке знаходиться в класі Parent, відмінно працює як з об’єктами класу Parent, так і з об’єктами будь-якого дочірнього класу (який наслідує клас Parent)!

В останньому випадку компілятор зіставляє посилання pref з operator<< класу Parent. Викликається віртуальна функція print(). Оскільки посилання pref фактично вказує на об’єкт класу Child, то викликається перевизначення Child::print(), як ми і припускали.

Проблема вирішена.

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (2 оцінок, середня: 5,00 з 5)
Loading...

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

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