Не вызывайте конструкторов из операции operator=( )
Хотя это правило говорит о перегруженном присваивании, на самом деле оно посвящено проблеме виртуальных функций. Соблазнительно реализовать operator=()
следующим образом:
class some_class
{
public:
virtual
~some_class( void );
some_class( void );
some_class( const some_class r );
const some_class operator=( const some_class r );
};
const some_class operator=( const some_class r )
{
if( this
!= r )
{
this-~some_class();
new(this) some_class(r);
}
return *this;
}
Этот вариант оператора new инициализирует указываемый this объект как объект some_class, в данном случае из-за аргумента r
используя конструктор копии.12
Есть серьезные причины не делать показанное выше. Во-первых, это не будет работать после наследования. Если вы определяете:
class derived : public some_class
{
public:
~derived();
// Предположим, что генерированная компилятором операция
// operator=() выполнится за операцией operator=() базового
// класса.
}
Вследствие того, что деструктор базового класса определен (правильно) как виртуальный, обращение предыдущего базового класса к:
this-~some_class()
вызывает деструктор производного класса, поэтому вы уничтожите значительно больше, чем намеревались. Вы можете попытаться исправить эту проблему, изменив вызов деструктора на:
this-some_class::~some_class();
Явное упоминание имени класса — some_class:: в этом примере —
подавляет механизм виртуальной функции. Функция вызывается, как если бы она не была виртуальной.
Деструктор не является единственной проблемой. Рассмотрим простое присваивание объектов производного класса:
derived d1, d2;
d1 = d2;
Операция производного класса operator=()
(вне зависимости от того, генерируется она компилятором или нет) образует цепочку с operator=()
базового класса, который в настоящем случае использует оператор new() для явного вызова конструктора базового класса. Конструктор, тем не менее, делает значительно больше, чем вы можете видеть в определении. В частности, он инициализирует указатель таблицы виртуальных функций так, чтобы он указывал на таблицу его класса. В текущем примере перед присваиванием указатель vtable
указывает на таблицу производного класса. После присваивания указатель vtable
указывает на таблицу базового класса; он был переинициализирован неявным вызовом конструктора при вызове new в перегруженной операции operator=().
Таким образом, вызовы конструкторов в операции operator=()
просто не будут работать, если есть таблица виртуальных функций. Так как вы можете знать или не знать, на что похожи определения вашего базового класса, то вы должны исходить из того, что таблица виртуальных функций имеется, и поэтому не вызывайте конструкторов.
Лучшим способом устранения дублирования кода в операции присваивания operator=()
является использование простой вспомогательной функции:
class some_class
{
void create ( void );
void create ( const some_class r );
void destroy ( void );
public:
virtual
~some_class( void
) { destroy(); }
some_class( void
) { create(); }
const some_class operator=( const some_class r );
};
inline const some_class some_class::operator=( const
some_class r )
{
destroy();
create( r );
}
inline some_class::some_class( void )
{
create();
}
~some_class::some_class( void )
{
destroy();
}
Часть 8е. Перегрузка операций