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

Возьмите за правило создавать конструктор копирования, если в классе описана хотя бы одна динамическая переменная.
  • Проблема. Передача объекта в функцию.

Объекты класса можно передавать в функции в качестве аргументов точно так же, как передаются данные других типов. Однако, следует помнить, что в языках С и С++ методом передачи параметров, по умолчанию является передача объектов по значению. Это означает, что внутри функции создается копия объекта - аргумента, и эта копия, а не сам объект, используется функцией. Следовательно, изменения копии объекта внутри функции не влияют на сам объект.

Объект внутри функции - это побитовая копия передаваемого объекта, а это значит, что если объект содержит в себе, например, некоторый указатель на динамически выделенную область памяти, то при копировании создается объект, указывающий на ту же область памяти. И как только вызывается деструктор копии, где, как правило, принято высвобождать память, то высвобождается область памяти, на которую указывал объект-"оригинал", что приводит к разрушению исходного объекта.

  • Проблема. Возврат объекта из функции.

Для того чтобы функция могла возвращать объект, нужно: во-первых, объявить функцию так, чтобы ее возвращаемое значение имело тип класса, во-вторых, возвращать объект с помощью обычного оператора return. Однако если возвращаемый объект содержит деструктор, то в этом случае возникают проблемы, связанные с "неожиданным" разрушением объекта.

  • Проблема. Инициализация одного объекта другим при создании.

В программировании есть еще один случай побитового копирования - инициализация одного объекта другим при создании:

# include <iostream>
using namespace std;

class ClassName
{
public:
	ClassName ()
	{
		cout << "ClassName!!!\n";
	}
	~ClassName ()
	{
		cout << "~ClassName!!!\n";
	}
};

void main()
{
	ClassName c1;

	// Вот он!!! Момент побитового копирования.
	ClassName c2=c1;
}

Результат работы программы:

ClassName!!!
~ClassName!!!
~ClassName!!!

Конструктор вызывается один раз: для с1. Для с2 конструктор не срабатывает. Однако деструктор срабатывает для обоих объектов. А, поскольку, с2 является точной копией с1, деструктор, высвобождающий динамически выделенную память, вызывается дважды для одного и того же фрагмента этой памяти. Это неминуемо приведет к ошибке.

Решение проблемы

Одним из способов обойти такого рода проблемы является создание особого типа конструкторов, - конструкторов копирования. Конструктор копирования или конструктор копии позволяет точно определить порядок создания копии объекта. Любой конструктор копирования имеет следующую форму:

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

Здесь obj - это ссылка на объект или адрес объекта. Конструктор копирования вызывается всякий раз, когда создается копия объекта. Таким образом, в конструкторе копирования можно выделить "свою" память под новый объект.

# include <iostream>
using namespace std;
class ClassName
{
public:
	ClassName ()
	{
		cout << "ClassName!!!\n";
	}
	ClassName (ClassName&obj){
		cout << "Copy ClassName!!!\n";
	}
	~ClassName ()
	{
		cout << "~ClassName!!!\n";
	}
};
void f(ClassName o){
	cout<<"Function f!!!\n";
}

ClassName r(){
	ClassName o;
	cout<<"Function r!!!\n";
	return o;
}
void main()
{
	// инициализация одного объекта другим
	ClassName c1;
	ClassName c2=c1;

	// передача объекта в функцию
	ClassName a;
	f(a);

	//возврат объекта из функции
	r();
	
}

Результат работы программы:

// создался объект с1
ClassName!!!

// инициализация объекта с2 объектом с1
Copy ClassName!!!

// создался объект а
ClassName!!!

// передача а в функцию по значению
// создалась копия о
Copy ClassName!!!

// отработала функция f
Function f!!!

// уничтожилась копия o
~ClassName!!!

// создался объект o
// внутри функции r
ClassName!!!

// отработала функция r
Function r!!!

// возврат из функции 
// создалась копия объекта о
Copy ClassName!!!

// уничтожился объект o
~ClassName!!!

// уничтожилась его копия
~ClassName!!!

// уничтожился объект а
~ClassName!!!

// уничтожился объект с2
~ClassName!!!

// уничтожился объект с1
~ClassName!!!
Конструктор копирования не влияет на операцию присваивания вида A=B. Здесь также срабатывает побитовое копирование, однако эту проблему решают в С++ иначе.

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

Помимо создания конструктора копирования есть другой способ организации взаимодействия между функцией и программой, передающей объект. Этот способ - передача объекта по ссылке или по указателю.
PQ VPS сервера в 28+ странах.