Правила программирования на С и С++. Главы 7-8 - Производные классы должны обычно определять конструктор копии и функцию operator=( )
ОГЛАВЛЕНИЕ
131. Производные классы должны обычно определять конструктор копии и функцию operator=( ).
При наследовании есть и другая связанная с копированием проблема. В одном месте руководства10 по языку С++ недвусмысленно заявлено: "конструкторы и функция operator=() не наследуются". Однако далее в этом же документе говорится, что существуют ситуации, в которых компилятор не может создать конструктор копии или функцию operator=(), которые бы корректно вызывались вслед за функциями базового класса. Так как нет практической разницы между унаследованной и сгенерированной функциями operator=(), которые ничего не делают, кроме вызова функции базового класса, то эта неопределенность вызвала много бед.
Я наблюдал два полностью несовместимых поведения компиляторов, столкнувшихся с этой дилеммой. Некоторые компиляторы считали правильным, чтобы сгенерированные компилятором конструкторы копий и функции operator=() вызывались автоматически после конструкторов и функций operator=() базового класса (и вложенного объекта).11 Это как раз тот способ, который, по мнению большинства, реализуется языком программирования. Другими словами, со следующим кодом проблем не будет:
class base{
public:
base( const base ?r );const base ?operator=( const base ?r );
};class derived : public base
{
string s;// нет операции operator=() или конструктора копии
};derived x;
derived y = x; // вызывает конструктор копии базового класса
// для копирования базового класса. Также вызывает// конструктор копии строки для копирования поля s.
x = y; // вызывает функцию базового класса operator=() для // копирования базового класса. Также вызывает строковую// функцию operator=() для копирования поля s.
Если бы все компиляторы работали таким образом, то проблемы бы не было. К несчастью, некоторые компиляторы принимают ту самую директиву "не наследуются" за чистую монету. Только что представленный код не будет работать с этими компиляторами. В них сгенерированные компилятором конструктор копии и функция operator=() производного класса действуют так, как будто бы их эквиваленты в базовом классе (и вложенном объекте) просто не существуют. Другими словами, конструктор по умолчанию - без аргументов - вызывается для копирования компонента базового класса, а почленное копирование - которое может выполняться просто функцией memcpy() - используется для поля. Мое понимание пересмотренного проекта стандарта С++ ISO/ANSI позволяет сделать вывод, что такое поведение некорректно, но в течение некоторого времени вам придется рассчитывать на худшее, чтобы обеспечивать переносимость. Следовательно, это, вероятно, хорошая мысль - всегда помещать в производный класс конструктор копии и функцию operator=(), которые явно вызывают своих двойников из базового класса. Вот реализация предыдущего производного класса для самого худшего случая: class derived : public base{
string s;public: derived( const derived ?r );const derived ?operator=( const derived ?r );
};//--------------------------------------------------------------
derived::derived( const derived ?r ) : base(r), s(r.s)
{}
//--------------------------------------------------------------
const derived ?derived::operator=( const derived ?r )
{
(* (base*)this) = r;s = r.s;
}Список инициализации членов в конструкторе копии описан ранее. Следующий отрывок из функции operator=() нуждается в некотором пояснении: (* (base*)this) = r;Указатель this указывает на весь текущий объект; добавление оператора приведения преобразует его в указатель на компонент базового класса в текущем объекте - (base*)this. (* (base*)this) является самим объектом, а выражение (* (base*)this) = r передает этому объекту сообщение, вызывая функцию operator=() базового класса для перезаписи информации из правого операнда в текущий объект. Вы могли бы заменить этот код таким образом: base::operator=( r );
но я видел компиляторы, которые бракуют этот оператор, если в базовом классе не объявлена явно функция operator=(). Первая форма работает независимо от того, объявлена явно operator=(), или нет. (Если не объявлена, то у вас будет по умолчанию реализовано почленное копирование).