}
Подразумевается, что функции-члену
copy()
доступны
sz
элементов как в аргументе
arg
, так и в векторе, в который он копируется. Для того чтобы обеспечить это, мы сделали функцию-член
copy()
закрытой. Ее могут вызывать только функции, являющиеся частью реализации класса vector. Эти функции должны обеспечить совпадение размеров векторов.
Конструктор копирования устанавливает количество элементов (
sz
) и выделяет память для элементов (инициализируя указатель
elem
) перед копированием значений элементов из аргумента
vector
.
vector::vector(const vector& arg)
// размещает элементы, а затем инициализирует их путем копирования
:sz(arg.sz), elem(new double[arg.sz])
{
copy(arg);
}
Имея конструктор копирования, мы можем вернуться к рассмотренному выше примеру.
vector v2 = v;
Это определение инициализирует объект
v2
, вызывая конструктор копирования класса
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;