Урок №170. Обрезка объектов

  Юрий Ворон  | 

    | 

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

 356

Вернемся к примеру с классами Parent и Child:

Здесь ссылка ref и указатель ptr ссылаются/указывают на объект child, который имеет как часть Parent, так и часть Child. Поскольку ref и ptr являются класса Parent, то они могут видеть часть Parent объекта child, часть Child объекта child все еще существует, но доступ к ней для ref или ptr закрыт. Однако, используя виртуальные функции, мы получаем доступ к наиболее дочернему методу. Следовательно, результат выполнения программы выше:

child is a Child and has value 7
ref is a Child and has value 7
ptr is a Child and has value 7

Но что бы произошло, если бы мы вместо создания ссылки или указателя класса Parent на объект класса Child, просто присвоили объект класса Child объекту класса Parent?

Помните, что child имеет как часть Parent, так и часть Child. Когда мы присваиваем объект класса Child объекту класса Parent, то копируется только часть Parent, часть Child не копируется. В примере выше parent получает копию части Parent объекта child, а часть Child объекта child «обрезается». Это называется обрезкой объектов (или просто «обрезкой»).

Поскольку переменная parent не имеет части Child, то parent.getName() вызывает Parent::getName().

Результат выполнения кода выше:

parent is a Parent and has value 7

Обрезка объектов и функции

Сейчас вы можете подумать, что пример выше нелепый. В конце концов, зачем нам присваивать объект child объекту parent таким образом? Повторять это, вы, скорее всего, не будете. Однако, обрезка объектов довольно таки нередко случается с функциями.

Например:

Это простая функция с константным объектом parent в качестве параметра, который передается по значению. Если мы будем вызывать эту функцию следующим образом:

То получим:

I am a Parent



Вы, наверное, не заметили, что parent является параметром-значением, а не параметром-ссылкой. При выполнении printName(ch), вы, наверное, ожидали, что parent.getName() вызовет переопределение getName(), которое выведет «I am a Child», но это не так. Вместо этого объект ch класса Child обрезается, и только часть Parent копируется в передаваемый параметр parent. При выполнении parent.getName(), несмотря даже на то, что функция getName() является виртуальной, для нее не существует части Child. Следовательно, получили то, что получили.

В этом случае всё довольно очевидно по тому, что выводится на экран. Но если у вас есть функции, которые ничего не выводят на экран, то отследить такую ошибку будет уже проблематично.

Конечно, обрезку здесь можно было бы легко избежать, используя передачу по ссылке вместо передачи по значению (вот еще одна причина, по которой передача классов по ссылке вместо передачи по значению является хорошей идеей):

Результат:

I am a Child

Обрезка векторов

Еще одна ошибка с которой сталкиваются новички при работе с обрезкой — попытка реализовать полиморфизм, используя std::vector. Добавим к нашей программе следующий код:

Результат выполнения программы выше:

I am a Parent with value 7
I am a Parent with value 8

Поскольку std::vector был объявлен как вектор класса Parent, то при добавлении Child(8) к нему, выполнилась обрезка объекта.

Исправить это немного сложнее. Новички пытаются сделать вектор с ссылками на объекты, например:

К сожалению, это не сработает. Элементы std::vector должны быть объектами, которым можно переприсваивать значения, тогда как ссылки могут быть инициализированы только раз и переприсваивать им значения нельзя.

Одним из способов решения этой проблемы является создание вектора с указателями на объекты:

Результат выполнения программы выше:

I am a Parent with value 7
I am a Child with value 8

Работает! Но это боль, так как теперь нам придется иметь дело с динамическим выделением памяти.

К счастью, есть еще один способ решения этой проблемы. Стандартная библиотека C++ предоставляет класс std::reference_wrapper. По сути, std::reference_wrapper — это класс, который работает как ссылка, но позволяет выполнять операции присваивания и копирования и совместим с std::vector.

Хорошей новостью является то, что вам не нужно знать, как он реализован, для того, чтобы его использовать. Всё, что вам нужно знать:

   std::reference_wrapper находится в заголовочном файле <functional>.

   При создании объекта класса std::reference_wrapper, этот объект не может быть анонимным (поскольку анонимные объекты имеют область видимости выражения, что может привести к висячей ссылке).

   Для получения объекта из std::reference_wrapper, используется метод get().

Перепишем наш код, добавив std::reference_wrapper:

Результат:

I am a Parent with value 7
I am a Child with value 8

Всё отлично и нам не нужно заморачиваться с динамическим выделением памяти.

Итого

Хотя C++ поддерживает присваивание объектов дочерних классов объектам родительского класса посредством обрезки объектов, это приносит больше боли, нежели пользы, поэтому мы рекомендуем избегать случаев с выполнением обрезки объектов. Когда дело доходит до работы с дочерними классами, всегда перепроверяйте параметрамы своих функций, чтобы ни в коем случае не выполнялась передача по значению.

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

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

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

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

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