Стандарты программирования на С++. 101 правило и рекомендация | страница 93



, если пользователям вашего класса необходимо получать полиморфные (полные, глубокие) копии.

Обсуждение

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

>class B { /* ... */ };


>class D : public B { /* ... */ };


>// Оп! Получение объекта по значению

>void Transmogrify(B obj);


>void Transubstantiate(B& obj) { // Все нормально -

>                                // передача по ссылке

> Transmogrify(obj);             // Плохо! Срезка объекта!

> // ...

>}


>D d;

>Transubstantiate(d);

Программист намерен работать с объектами и производных классов полиморфно. Однако, по ошибке (или усталости — к тому же и кофе закончился…) программист или просто забыл написать >& в сигнатуре >Transmogrify, или собирался создать копию, но сделал это неверно. Код компилируется без ошибок, но когда функция >Transmogrify вызывается с передачей ей объекта >D, он мутирует в объект B. Это связано с тем, что передача по значению приводит к вызову >B::В(const B&), т.е. копирующего конструктора , параметр которого >const B& представляет собой автоматически преобразованную ссылку на >d. Что приводит к полной потере динамического, полиморфного поведения, из-за которого в первую очередь и используется наследование.

Если, как автор класса , вы хотите разрешить срезку, но не хотите, чтобы она могла происходить по ошибке, для такого случая существует один вариант действий, о котором мы упомянем для полноты изложения, но не рекомендуем использовать его в коде, к которому предъявляется требование переносимости: вы можете объявить копирующий конструктор как >explicit. Это может помочь избежать неявной срезки, но кроме этого запрещает все передачи параметров данного типа по значению (что может оказаться вполне приемлемым для базовых классов, объекты которых все равно не должны создаваться; см. рекомендацию 35).

>// Объявляем копирующий конструктор как explicit (у данного

>// решения имеется побочное действие, так что требуется

>// улучшение этого метода)

>class B { // ...

>public:

> explicit B(const B& rhs);

>};


>class D : public B { /* ... */ };

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