Читаем Программирование полностью

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

Мы обязательно обсудим проблему манипуляции памятью без помощи высокоуровневых типов, таких как vector и string, изучим массивы и указатели, их взаимосвязь и способы применения, а также ловушки, связанные с их использованием. Это важная информация для любого программиста, вынужденного работать с низкоуровневыми кодами, написанными на языке C++ или C.

Отметим, что детали класса vector характерны не только для векторов, но и для других высокоуровневых типов, которые создаются из низкоуровневых. Однако каждый высокоуровневый тип (string, vector, list, map и др.) в любом языке создается из одинаковых машинных примитивов и отражает разнообразие решений фундаментальных проблем, описанных в этой главе.

<p id="AutBody_Root322"><strong>18.2. Копирование</strong></p>

Рассмотрим класс vector в том виде, в каком он был представлен в конце главы 17.

class vector {

  int sz; // размер

  double* elem; // указатель на элементы

public:

  vector(int s) // конструктор

  :sz(s), elem(new double[s]) { /* */ } // выделяет

                                        // память

  ~vector()           // деструктор

  { delete[ ] elem; } // освобождает

  // память

  // ...

};

Попробуем скопировать один из таких векторов.

void f(int n)

{

  vector v(3);   // определяем вектор из трех элементов

  v.set(2,2.2);  // устанавливаем v[2] равным 2.2

  vector v2 = v; // что здесь происходит?

 // ...

}

Теоретически объект v2 должен стать копией объекта v (т.е. оператор = создает копии); иначе говоря, для всех i в диапазоне [0:v.size()] должны выполняться условия v2.size()==v.size() и v2[i]==v[i]. Более того, при выходе из функции f() вся память возвращается в свободный пул. Именно это (разумеется) делает класс vector из стандартной библиотеки, но не наш слишком простой класс vector. Наша цель — улучшить наш класс vector, чтобы правильно решать такие задачи, но сначала попытаемся понять, как на самом деле работает наша текущая версия. Что именно она делает неправильно, как и почему? Поняв это, мы сможем устранить проблему. Еще более важно то, что мы можем распознать аналогичные проблемы, которые могут возникнуть в других ситуациях.

По умолчанию копирование относительно класса означает “скопировать все данные-члены”. Это часто имеет смысл. Например, мы копируем объект класса Point, копируя его координаты. Однако при копировании членов класса, являющихся указателями, возникают проблемы. В частности, для векторов в нашем примере выполняются условия v.sz==v2.sz и v.elem==v2.elem, так что наши векторы выглядят следующим образом:

Иначе говоря, объект v2 не содержит копии элементов объекта v; он ими владеет совместно с объектом v. Мы могли бы написать следующий код:

v.set(1,99);  // устанавливаем v[1] равным 99

v2.set(0,88); // устанавливаем v2[0] равным 88

cout << v.get(0) << ' ' << v2.get(1);

В результате мы получили бы вектор 88 99. Это не то, к чему мы стремились. Если бы не существовало скрытой связи между объектами v и v2, то результат был бы равен 0 0, поскольку мы не записывали никаких значений в ячейку v[0] или v2[1]. Вы могли бы возразить, что такое поведение является интересным, аккуратным или иногда полезным, но мы не этого ждали, и это не то, что реализовано в стандартном классе vector. Кроме того, когда мы вернем результат из функции f(), произойдет явная катастрофа. При этом неявно будут вызваны деструкторы объектов v и v2; деструктор объекта v освободит использованную память с помощью инструкции

delete[] elem;

И то же самое сделает деструктор объекта v2. Поскольку в обоих объектах, v и v2, указатель elem ссылается на одну ту же ячейку памяти, эта память будет освобождена дважды, что может привести к катастрофическим результатам (см. раздел 17.4.6). 

<p id="AutBody_Root323"><strong>18.2.1. Конструкторы копирования</strong></p>
Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже