По умолчанию при инициализации одного объекта другим С++ выполняет побитовое копирование. Это означает, что точная копия инициализирующего объекта создается в целевом объекте. Хотя в большинстве случаев такой способ инициализации объекта является вполне приемлемым, имеются случаи, когда побитовое копирование не может использоваться. Например, такая ситуация имеет место, когда объект выделяет память при своем создании. Рассмотрим в качестве примера два объекта А и В класса 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=а выполняет операцию присваивания. Если оператор = не перегружен, то будет сделана побитовая копия. Поэтому в определенных случаях требуется перегрузить оператор = в дополнение к созданию конструктора копирования.