Литмир - Электронная Библиотека
A
A

  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;

Программирование. Принципы и практика использования C++ Исправленное издание - _199.png

Теперь в классе

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Реализуя присваивание, код можно упростить, освобождая память, занятую старыми элементами, до создания копии, но обычно не стоит стирать информацию, если вы не уверены, что ее можно заменить. Кроме того, если вы это сделаете, то при попытке присвоить объект класса 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 

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

Программирование. Принципы и практика использования C++ Исправленное издание - _200.png

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

int* p = new int(77);

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

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

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

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

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

Программирование. Принципы и практика использования C++ Исправленное издание - _201.png

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 Используя эту терминологию, мы можем сказать, что проблема с нашим исходным классом
vector
заключалась в том, что мы выполняли поверхностное копирование и не копировали элементы, на которые ссылался указатель
elem
. Наш усовершенствованный класс
vector
, как и стандартный класс
vector
, выполняет глубокое копирование, выделяя новую память для элементов и копируя их значения. О типах, предусматривающих поверхностное копирование (таких как указатели и ссылки), говорят, что они имеют семантику указателей (pointer semantics) или ссылок (reference semantics), т.е. копируют адреса. О типах, осуществляющих глубокое копирование (таких как
string
и
vector
), говорят, что они имеют семантику значений (value semantics), т.е. копируют значения, на которые ссылаются. С точки зрения пользователя типы с семантикой значений функционируют так, будто никакие указатели не используются, а существуют только значения, которые копируются. С точки зрения копирования типы, обладающие семантикой значений, мало отличаются от типа
int

18.3. Основные операции

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Настал момент, когда мы можем приступить к обсуждению того, какие конструкторы должен иметь класс, должен ли он содержать деструктор и требуется ли копирующее присваивание. Следует рассмотреть пять важных операций.

• Конструкторы с одним или несколькими аргументами.

• Конструктор по умолчанию.

• Копирующий конструктор (копирование объектов одинаковых типов).

• Копирующее присваивание (копирование объектов одинаковых типов).

• Деструктор.

Обычно класс должен иметь один или несколько конструкторов, аргументы которых инициализируют объект.

string s(" Триумф "); // инициализируем объект s строкой "Триумф"

vector<double> v(10); // создаем вектор v, состоящий из 10 чисел

                      // double

Как видим, смысл и использование инициализатора полностью определяются конструктором. Стандартный конструктор класса

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

241
{"b":"847443","o":1}