Урок №172. Вывод объектов классов через оператор вывода

  Юрий Ворон  | 

    | 

  Обновлено 3 Ноя 2018  | 

 290

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

Здесь понятно, что p.print() вызывает Child::print() (поскольку p ссылается на объект класса Child, Parent::print() является виртуальной функцией, а Child::print() является переопределением).

Такой способ вывода неплох, но с std::cout не очень хорошо сочетается:

В этом уроке мы рассмотрим, как переопределить оператор вывода << для классов с наследованием, чтобы иметь возможность использовать оператор << следующим образом:

Проблема с переопределением operator<<

Начнем с обычной перегрузки оператора вывода <<:

Поскольку здесь нет виртуальных функций, то всё довольно просто и ясно:

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<< производил вывод самостоятельно, мы делегируем эту задачу обычному методу, который является виртуальной функцией!

Рассмотрим это на практике:

Программа выше работает у всех трёх случаях:

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 (7 оценок, среднее: 5,00 из 5)
Загрузка...

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

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

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