Шрифт:
Интервал:
Закладка:

Теперь деструктор может работать правильно. Каждый набор элементов будет корректно удален. Очевидно, что два объекта класса 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. Копирующее присваивание

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 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
Эту ситуацию можно проиллюстрировать так.

