Правила программирования на Си и Си++

       

Производные классы должны обычно определять конструктор копии и функцию 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=(), или нет. (Если не объявлена, то у вас будет по умолчанию реализовано почленное копирование).

Содержание раздела