Урок №162. Указатели/Ссылки и Наследование

  Юрий Ворон  | 

    | 

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

 1112

 ǀ   4 

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

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

Из урока №155 мы знаем, что при создании объекта дочернего класса выполняется построение 2-ух частей из которых этот объект и состоит: родительская и дочерняя.

Например:

При создании объекта класса Child сначала выполняется построение части Parent, а затем уже части Child. Помните, что тип отношений в наследовании — «является». Поскольку Child «является» Parent, то и логично, что Child содержит часть Parent.



Указатели, ссылки и дочерние классы

Мы можем указать указателям и ссылкам класса Child указывать на другие объекты класса Child:

Результат:

child is a Child and has value 7
rChild is a Child and has value 7
pChild is a Child and has value 7

Интересно, поскольку Child имеет часть Parent, то можем ли мы указать указателю или ссылке класса Parent указывать на объект класса Child? Оказывается, можем!

Результат:

child is a Child and has value 7
rParent is a Parent and has value 7
pParent is a Parent and has value 7

Но это может быть не совсем то, что вы ожидали увидеть!

Поскольку rParent и pParent являются ссылкой и указателем класса Parent, они могут видеть только члены класса Parent (и члены любых других классов, которые наследует Parent). Таким образом, указатель/ссылка класса Parent не может видеть Child::getName(). Следовательно, вызывается Parent::getName() и rParent и pParent сообщают, что они являются Parent, а не Child.

Обратите внимание, это также означает, что невозможно вызвать Child::getValueDoubled() через rParent или pParent. Они не могут видеть что-либо в классе Child.

Вот еще один более сложный пример:

Результат:

cat is named Matros, and it says Meow
dog is named Barsik, and it says Woof
pAnimal is named Matros, and it says ???
pAnimal is named Barsik, and it says ???

Мы видим здесь ту же проблему. Поскольку pAnimal — это указатель типа Animal, то он может видеть только часть Animal. Следовательно, pAnimal->speak() вызывает Animal::speak(), а не Dog::Speak() или Cat::speak().

Указатели, ссылки и родительские классы

Теперь вы можете сказать: «Примеры выше кажутся глупыми. Почему я должен использовать указатель или ссылку родительского класса на объект дочернего класса, если я могу просто использовать дочерний объект?». Оказывается, на это есть несколько веских причин.

Во-первых, предположим, вы хотите написать функцию, которая выводит имя и звук животного. Без использования указателя на родительский класс, вам придется реализовать это через перегрузку функций. Например:

Не слишком сложно, но подумайте, что было бы, если бы у нас было 30 разных типов животных. Нам пришлось бы написать 30 перегрузок! Кроме того, если вы когда-либо добавите новый тип животных, вам также придется написать новую функцию для этого типа животных. Это огромная трата времени.



Однако, поскольку Cat и Dog наследуют Animal, Cat и Dog имеют часть Animal. Поэтому мы можем сделать следующее:

Это позволит нам передавать любой класс, который является дочерним классу Animal! Вместо отдельного метода на каждый дочерний класс мы записали один метод, который работает сразу со всеми дочерними классами!

Проблема, конечно, в том, что, поскольку rAnimal является ссылкой класса Animal, то rAnimal.speak() вызовет Animal::speak() вместо метода speak() дочернего класса.

Во-вторых, допустим, у нас есть 3 кошки и 3 собаки, которых мы бы хотели сохранить в массиве для легкого доступа к ним. Поскольку массивы могут содержать объекты только одного типа, то без указателей/ссылок на родительский класс, нам бы пришлось создавать отдельный массив для каждого дочернего класса, например:

Теперь подумайте, что произошло бы, если бы у нас было 30 разных типов животных. Нам пришлось бы создать 30 массивов, по одному на каждый тип животных!

Однако, поскольку Cat и Dog наследуют Animal, то есть смысл в следующем:

Хотя это скомпилируется и выполнится, но, к сожалению, тот факт, что каждый элемент массива animals является указателем на Animal, означает, что animals[iii]->speak() будет вызывать Animal::speak() вместо методов speak() дочерних классов.

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

Угадайте теперь, зачем нужны виртуальные функции? 🙂

Тест

Наш пример с Animal/Cat/Dog не работает так, как мы хотим, потому что ссылка/указатель класса Animal не может получить доступ к методам speak() дочерних классов. Один из способов обойти эту проблему — сделать так, чтобы данные, возвращаемые методом speak() стали доступными в виде родительской части класса Animal (так же, как name класса Animal доступен через член m_name).

Обновите классы Animal, Cat и Dog в коде выше, добавив новый член m_speak в класс Animal. Инициализируйте его соответствующим образом. Следующая программа должна работать корректно:

Ответ

Примечание: Вы также можете сделать m_speak типа std::string, но недостатком будет то, что каждый объект класса Animal будет содержать лишнюю копию строки speak, а построение объектов Animal займет больше времени, так как глубокое копирование std::string будет выполняться медленнее, нежели копирование указателя, указывающего на константную строку C-style.

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

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

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

  1. Vlad:

    Спасибо за интересную статью, когда будут потоки?)

    1. Юрий Ворон Юрий Ворон:

      Пожалуйста. Какие потоки?

      1. Vlad:

        многопоточность С++, всмысле.

        1. Юрий Ворон Юрий Ворон:

          Чуть позднее.

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

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

telegram канал
RAVESLI