Программирование на C и C++

Онлайн справочник программиста на C и C++

Конструктор копирования

По умолчанию при инициализации одного объекта другим С++ выполняет побитовое копирова­ние. Это означает, что точная копия инициализирующего объекта создается в целевом объекте. Хотя в большинстве случаев такой способ инициализации объекта является вполне приемлемым, имеются случаи, когда побитовое копирование не может использоваться. Например, такая ситу­ация имеет место, когда объект выделяет память при своем создании. Рассмотрим в качестве при­мера два объекта А и В класса ClassType, выделяющего память при создании объекта. Положим, что объект А уже существует. Это означает, что объект A уже выделил память. Далее предполо­жим, что А использовался для инициализации объекта B, как показано ниже:

ClassType В = А;

Если в данном случае используется побитовое копирование, то В станет точной копией А. Это означает, что В будет использовать тот же самый участок выделенной памяти, что и A, вместо того, чтобы выделить свой собственный. Ясно, что такая ситуация нежелательна. Например, если класс ClassType включает в себя деструктор, освобождающий память, то тогда одна и та же па­мять будет освобождаться дважды при уничтожении объектов A и B!

Проблема того же типа может возникнуть еще в двух случаях. Первый из них возникает, когда копия объекта создается при передаче в функцию объекта в качестве аргумента. Второй случай возникает, когда временный объект создается функцией, возвращающей объект в качестве своего значения. (Не надо забывать, что временные объекты автоматически создаются, чтобы содержать возвращаемое значение функции, и они могут также создаваться в некоторых других ситуациях.)

Для решения подобных проблем язык С++ позволяет создать конструктор копирования, кото­рый используется компилятором, когда один объект инициализирует другой. При наличии кон­структора копирования побитовое копирование не выполняется. Общая форма конструктора ко­пирования имеет вид:

имя_класса (const имя_класса &о) {
// тело конструктора
}

Здесь о является ссылкой на объект в правой части инициализации. Конструктор копирования может иметь также дополнительные параметры, если для них определены значения по умолча­нию. Однако в любом случае первым параметром должна быть ссылка на объект, выполняющий инициализацию.

Инициализация возникает в трех случаях: когда один объект инициализирует другой, когда ко­пия объекта передается в функцию и когда создается временный объект (обычно если он служит возвращаемым значением). Например, любая из следующих инструкций вызывает инициализацию:

myclass х = у; // инициализация
func (х); // передача параметра
у = func (); // получение временного объекта

Ниже приведен пример, где необходим явный конструктор копирования. Эта программа со­здает очень простой «безопасный» тип массива целых чисел, предотвращающий вывод за грани­цы массива. Память для каждого массива выделяется с использованием оператора new и в каж­дом объекте поддерживается работа с указателем на выделенную память.

/* Данная программа создает класс "безопасный" массив.
Поскольку место для массива выделено с помощью new, то для выделения памяти в случае, когда один объект используется для инициализации другого, предоставляется конструктор копирования
*/
#include <iostream.h>
#include <stdlib.h>
class array {
int *p;
int size;
public:
array(int sz) {
p = new int[sz];
if ( !p) exit(1);
size = sz;
}
~array() {delete [] p; }
// конструктор копирования
array(const array &a);
void put(int i, int j) {
if (i>=0 && i<size) p[i] = j;
}
int get (int i) {
return p[i];
}
};
/ / конструктор копирования
array::array(const array &a) {
int i;
p = new int[a.size];
if ( !p) exit(1);
for(i=0; i<a.size; i++) p[i] = a.p[i];
}
int main()
{
array num(10);
int i;
for(i=0; i<10; i++) num.put(i, i);
for(i=9; i>=0; i--) cout << num.get(i);
cout << "\n";
// создание другого массива и инициализация его значениями
num array x(num); // вызов конструктора копирования
for (i=0; i<10; i++) cout << x.get(i);
return 0;
}

Когда объект num используется для инициализации х, то вызывается конструктор копирова­ния, выделяющий новую память, адрес которой помещается в х.р, а затем содержание массива num копируется в массив объекта х. Таким образом, объекты х и num содержат массивы с одина­ковыми значениями, но каждый массив независим от другого и располагается в своей области памяти. Если бы конструктор копирования не был создан, то использовалась бы инициализация по умолчанию путем побитового копирования, так что массивы х и num разделяли бы между собой одну и ту же область памяти.

Конструктор копирования выделяется только для инициализации. Например, следующие инст­рукции не содержат вызова конструктора копирования:

array а (10);
...
array b (10);
b = а; //не вызывает конструктор копирования

В данном случае b=а выполняет операцию присваивания. Если оператор = не перегружен, то будет сделана побитовая копия. Поэтому в определенных случаях требуется перегрузить опера­тор = в дополнение к созданию конструктора копирования.