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

Когда новички изучают программирование, первым делом, при рассмотрении новой темы, возникает вопрос — для чего необходима та или иная «вещь» о которой сейчас предстоит узнать. Ответим сразу на этот вопрос: «Зачем нужен конструктор копирования?».

Конструктор копирования необходим для того, чтобы мы могли создавать «реальные» (а не побитовые) копии для объектов класса. Такая копия объекта может понадобиться в следующих случаях:

  • при передаче объекта класса в функцию, как параметра по значению (а не по ссылке);
  • при возвращении из функции объекта класса, как результата её работы;
  • при инициализации одного объекта класса другим объектом этого класса.

При передаче объекта в функцию как параметра по значению, эта функция начнет работать с его побитовой копией, а не с полями самого объекта. Допустим определены конструктор и деструктор класса. Первый память выделяет, а второй её освобождает. Во время работы функции, указатель побитовой копии объекта указывает на адрес памяти, где расположен оригинальный объект. В то время, когда работа функции завершается — удаляется и побитовая копия объекта. При ее удалении обязательно сработает определённый деструктор и освободит ту память, что занята объектом-оригиналом. Программа продолжит работу, и при завершении работы, деструктор сработает повторно, пытаясь освободить все тот же отрезок памяти. Это вызовет ошибку программы.

Использование конструктора копирования — прекрасный способ обойти эти ошибки и проблемы. Он создаст «реальную» копию объекта, которая будет иметь личную область динамической памяти.

Конструктор копирования синтаксически выглядит так:

НазваниеКласса (const НазваниеКласса& object)

{

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

}

Ниже разберём несложный, но очень показательный пример. В нём будут рассмотрены все 3 случая в которых желательно применять конструктор копирования. Будет создан класс, содержащий конструктор без параметров, конструктор копирования и деструктор. Чтобы пример был не слишком громоздким, конструкторы и деструктор будут выводить на экран сообщения типа «Сработал конструктор», «Сработал дектруктор»… Выделять и освобождать память не будем. Нам отлично будет видно сколько раз сработают конструкторы а сколько раз деструктор. Очевидно, что деструктор (если бы он освобождал память) не должен срабатывать большее количество раз, чем конструктор, выделяющий память.

Пример:

#include <iostream>

using namespace std;

 

class OneClass

{

int *ptr; // какой-то указатель

 

public:

OneClass() // конструктор без параметров

{

cout << "\nСработал Конструктор без параметров\n";

}

 

OneClass(const OneClass &object)

{

cout << "\nСработал Конструктор копирования\n";

}

 

~OneClass()

{

cout << "\nСработал Дестркуктор\n";

}

};

 

void showFunc(OneClass object)

{

cout << "\nЭта функция принимает объект класса, как параметр.\n";

cout << "Сначала срабатывает конструктор копирования (т.к. создается реальная копия объекта).\n";

cout << "Затем выполняется код функции. А при выходе из нее - сработает деструктор.\n";

}

 

OneClass returnObjectFunc()

{

OneClass object; // тут сработает конструктор без параметров

cout << "\nЭта функция возвращает объект.\n";

cout << "При выходе из нее, деструктор сработает дважды.\n";

return object; // тут сработает конструктор копирования

}

 

int main()

{

setlocale(LC_ALL, "rus");

cout << "~~~~~~~~~~~~~~~  Блок 1  ~~~~~~~~~~~~~~~\n";

OneClass object1; // объявляем объект класса

 

cout << "\n~~~~~~~~~~~~~~~  Блок 2  ~~~~~~~~~~~~~~~\n";

showFunc(object1); // передаем объект в showFunc()

 

cout << "\n~~~~~~~~~~~~~~~  Блок 3  ~~~~~~~~~~~~~~~\n";

returnObjectFunc(); // функция returnObjectFunc() возвращает объект

 

cout << "\n~~~~~~~~~~~~~~~  Блок 4  ~~~~~~~~~~~~~~~\n";

OneClass object2 = object1;  // инициализация объекта object2 при создании

// при завершении программы деструктор сработает дважды

// раз для object2, второй раз для object1

}

Конструктор без параметров будет вызываться во время создания новых объектов класса. Конструктор копирования — во время создания копий объекта. Деструктор срабатывает при удалении и реального объекта и его копии. В теле функций все описано подробно и не требует дополнительных комментариев.

Запустив программу увидим в консоли следующее:

Посмотрим что программа выдала в консоль. Блок 1 — во время создания нового объекта, сработал конструктор без параметров. В блоке 2 мы разместили функцию showFunc(). Во время передачи в неё «объекта-параметра» по значению, сработал конструктор копирования и создалась «реальная» копия объекта класса OneClass. При выходе из этой функции сработал деструктор, так как копия объекта уничтожается. Кстати, то, что передача объекта как параметра по значению, вызывает конструктор копирования, служит отличным поводом для передачи объекта по ссылке. Это сэкономит и время и память.

В блоке 3 размещена функция returnObjectFunc(). Так как в её теле прописано создание нового объекта класса OneClass — сначала сработал конструктор без параметров. Далее выполняется код функции и во время возврата объекта в главную функцию main, сработал конструктор копирования. В конце, как и должно быть, деструктор отработал дважды: для объекта и для его реальной копии.

В четвертом блоке, во время объявления и инициализации нового объекта object2, сработал конструктор копирования. При завершении работы программы деструктор сработал для копии объекта из четвертого блока и для объекта object1 из первого блока.

Если же мы закомментируем /*конструктор копирования*/ в классе и снова запустим программу — увидим, что конструктор без параметров сработает 2 раза, а деструктор — пять раз отработает.

В этой ситуации, если бы деструктор освобождал память — в программе возникла бы ошибка.