Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 177 178 179 180 181 182 183 184 185 ... 337
class="code">vector с аргументом v. Если бы объект класса vector содержал три элемента, то возникла бы следующая ситуация:

Теперь деструктор может работать правильно. Каждый набор элементов будет корректно удален. Очевидно, что два объекта класса vector теперь не зависят друг от друга, и мы можем изменять значения элементов в объекте v, не влияя на содержание объекта v2, и наоборот. Рассмотрим пример.

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

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

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

Результат равен 0 0.

Вместо инструкции

vector v2 = v;

мы могли бы написать инструкцию

vector v2(v);

Если объекты v (инициализатор) и v2 (инициализируемая переменная) имеют одинаковый тип и в этом типе правильно реализовано копирование, то приведенные выше инструкции эквивалентны, а их выбор зависит от ваших личных предпочтений. 

18.2.2. Копирующее присваивание

 

 Копирование векторов может возникать не только при их инициализации, но и при присваивании. Как и при инициализации, по умолчанию копирование производится поэлементно, так что вновь может возникнуть двойное удаление (см. раздел 18.2.1) и утечка памяти. Рассмотрим пример.

void f2(int n)

{

  vector v(3); // определяем вектор

  v.set(2,2.2);

  vector v2(4);

  v2 = v;      // присваивание: что здесь происходит?

  // ...

}

Мы хотели бы, чтобы вектор v2 был копией вектора v (именно так функционирует стандартный класс vector), но поскольку в нашем классе vector смысл копирования не определен, используется присваивание по умолчанию; иначе говоря, присваивание выполняется почленно, и члены sz и elem объекта v2 становятся идентичными элементам sz и elem объекта v соответственно.

Эту ситуацию можно проиллюстрировать следующим образом:

При выходе из функции f2() возникнет такая же катастрофа, как и при выходе из функции f() в разделе 18.2, до того, как мы определили копирующий конструктор: элементы, на которые ссылаются оба вектора, v и v2, будут удалены дважды (с помощью оператора delete[]). Кроме того, возникнет утечка памяти, первоначально выделенной для вектора v2, состоящего из четырех элементов. Мы “забыли” их удалить. Решение этой проблемы в принципе не отличается от решения задачи копирующей инициализации (см. раздел 18.2.1). Определим копирующий оператор присваивания.

class vector {

  int sz;

  double* elem;

  void copy(const vector& arg); // копирует элементы из arg

                                // в *elem

public:

  vector& operator=(const vector&) ; // копирующее присваивание

  // ...

};

vector& vector::operator=(const vector& a)

 // делает этот вектор копией вектора a

{

  double* p = new double[a.sz]; // выделяем новую память

  for (int=0; i<asz; ++i)

    p[i]=a.elem[i];             // копируем элементы

  delete[] elem;                // освобождаем память

  elem = p;                     // теперь можно обновить elem

  sz = a.sz;

  return *this;      // возвращаем ссылку

                     // на текущий объект (см. раздел 17.10)

}

Присваивание немного сложнее, чем создание, поскольку мы должны работать со старыми элементами. Наша основная стратегия состоит в копировании элементов из источника класса vector.

double* p = new double[a.sz]; // выделяем новую память

for(int=0; i<asz; ++i) p[i]=a.elem[i];

Теперь освобождаем старые элементы из целевого объекта класса vector.

delete[] elem; // освобождаем занятую память

В заключение установим указатель elem на новые элементы.

elem = p; // теперь можем изменить указатель elem

sz = a.sz;

Теперь в классе vector утечка памяти устранена, а память освобождается только один раз (delete[]).

 

 Реализуя присваивание, код можно упростить, освобождая память, занятую старыми элементами, до создания копии, но обычно не стоит стирать информацию, если вы не уверены, что ее можно заменить. Кроме того, если вы это сделаете, то при попытке присвоить объект класса vector самому себе могут возникнуть странные вещи.

vector v(10);

v=v; // самоприсваивание

Пожалуйста, убедитесь, что наша реализация функционирует правильно (если не оптимально). 

18.2.3. Терминология, связанная с копированием

Копирование встречается в большинстве программ и языков программирования. Основная проблема при этом заключается в том, что именно копируется: указатель (или ссылка) или информация, на которую он ссылается.

Поверхностное копирование (shallow copy) предусматривает копирование только указателя, поэтому в результате на один и тот же объект могут ссылаться два указателя. Именно этот механизм копирования лежит в основе работы указателей и ссылок.

Глубокое копирование (deep copy) предусматривает копирование информации, на которую ссылается указатель, так что в результате два указателя ссылаются на разные объекты. На основе этого механизма копирования реализованы классы vector, string и т.д. Если мы хотим реализовать глубокое копирование, то должны реализовать в наших классах конструктор копирования и копирующее присваивание.

Рассмотрим пример поверхностного копирования.

int* p = new int(77);

int* q = p; // копируем указатель p

*p = 88;    // изменяем значение переменной int, на которую

            // ссылаются указатели p и q 

Эту ситуацию можно проиллюстрировать следующим образом.

В противоположность этому мы можем осуществить глубокое копирование.

int* p = new int(77);

int* q = new int(*p); // размещаем новую переменную int,

                      // затем копируем значение, на которое

                      // ссылается p

*p = 88;              // изменяем значение, на которое ссылается p

Эту ситуацию можно проиллюстрировать так.

 

 Используя эту терминологию, мы можем сказать, что проблема с нашим исходным классом vector заключалась в том, что мы выполняли поверхностное копирование и не копировали элементы, на которые ссылался указатель elem. Наш усовершенствованный класс vector, как и стандартный класс vector, выполняет глубокое копирование, выделяя новую память для элементов и копируя их значения. О типах, предусматривающих поверхностное копирование
1 ... 177 178 179 180 181 182 183 184 185 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий