Думаю, главный вопрос, при изучении любой темы по программированию – Зачем? И на него надо отвечать в первую очередь, а потом уже показывать, как и когда надо применять те или иные инструменты программирования.
Конструктор копирования нужен нам для того, чтобы создавать «реальные» копии объектов класса, а не побитовую копию объекта. Иногда это принципиально важно. Такую «реальную» копию объекта надо создавать в нескольких случаях:
- когда мы передаем объект в какую-либо функцию в виде параметра;
- когда какая-либо функция должна вернуть объект класса в результате своей работы;
- когда мы в главной функции один объект класса инициализируем другим объектом класса.
Например, мы передаем объект в функцию в виде параметра. Функция будет работать не с самим переданным объектом, а с его побитовой копией. Допустим в конструкторе класса, при создании объекта, выделяется определенный объем памяти, а деструктор класса эту память освобождает. Указатель побитовой копии объекта будет хранить тот же адрес памяти, что и оригинальный объект. И, когда при завершении работы функции и уничтожении побитовой копии объекта, сработает деструктор, он обязательно освободит память, которая была занята объектом-оригиналом. В придачу, еще и при завершении работы программы, деструктор сработает повторно и попытается еще раз освободить этот объем памяти, что неизбежно приведет к ошибкам программы. Та же участь постигнет и память, выделенную для указателя объекта, если будет удаляться побитовая копия возвращаемого функцией объекта, и побитовая копия при инициализации объекта класса другим объектом.
Чтобы избежать этих проблем и ошибок существует конструктор копирования. Его работа заключается в том, чтобы создать реальную копию объекта со своей личной выделенной динамической памятью. Синтаксис конструктора копирования следующий:
имяКласса (const имяКласса & object) { //код конструктора копирования }
Рассмотрим простой пример. В нем создадим класс, который будет содержать обычный конструктор, конструктор копирования и деструктор. В этом примере будут описаны все три случая для которых надо применять конструктор копирования. Для того, чтобы пример не был слишком большим, мы не будем заставлять конструкторы выделять память, а деструктор освобождать память. Пропишем в них только вывод определенного сообщения на экран. Таким образом, можно будет посмотреть, сколько раз сработают конструкторы и деструктор. Как вы понимаете, если бы деструктор освобождал память, то количество его вызовов не должно превышать количество вызовов конструкторов.
Пример:
#include <iostream> using namespace std; class SomeClass { int *ptr; // указатель на какой-либо участок памяти public: SomeClass() // конструктор { cout << "\nОбычный конструктор\n"; } SomeClass(const SomeClass &obj) { cout << "\nКонструктор копирования\n"; } ~SomeClass() { cout << "\nДестркуктор\n"; } }; void funcShow(SomeClass object) { cout << "\nФункция принимает объект, как параметр\n"; } SomeClass funcReturnObject() { SomeClass object; cout << "\nФункция возвращает объект\n"; return object; } int main() { setlocale(LC_ALL,"rus"); cout << "1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; SomeClass obj1; // создаем объект класса cout << "2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; funcShow(obj1); // передаем объект в функцию cout << "3 - 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; funcReturnObject(); // эта функция возвращает объект cout << "5 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"; SomeClass obj2 = obj1; // инициализация объекта при создании }
В строках 4 — 21 определен класс. Обычный конструктор, строки 9 — 12, будет автоматически вызваться при создании новых объектов класса. А конструктор копирования, строки 13 — 16, будет автоматически вызваться при создании копии объекта. Деструктор, строки 17 — 20, вызывается всякий раз когда удаляется либо объект, либо его копия. Ниже определены две функции funcShow()
и funcReturnObject()
. Первая принимает объект в виде параметра, вторая при вызове во-первых создает новый объект (тут сработает обычный конструктор), а во-вторых возвращает объект (тут сработает конструктор копирования т.к. при возврате объекта из функции создается его временная копия).
Вот какую картинку мы увидим при запуске программы:
1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Обычный конструктор
2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Конструктор копирования
Функция принимает объект, как параметр
Дестркуктор
3 — 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Обычный конструктор
Функция возвращает объект
Конструктор копирования
Дестркуктор
Дестркуктор
5 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Конструктор копирования
Дестркуктор
Дестркуктор
Для продолжения нажмите любую клавишу . . .
Пойдем по порядку. В первом блоке сработал обычный конструктор — это произошло при создании объекта класса obj1
. Второй блок отображает работу функции funcShow()
. Так как при передаче объекта в функцию создается его копия и сработал конструктор копирования. А при завершении работы функции эта копия уничтожается и срабатывает деструктор. Блок 3 — 4 показывает, что при создании нового объекта в функции funcReturnObject()
сработал обычный конструктор, а при возврате объекта — конструктор копирования. Деструктор, как положено отработал дважды — для оригинала объекта и для копии. И наконец в пятом блоке срабатывает конструктор копирования при инициализации нового объекта класса. Затем дважды деструктор — один уничтожает копию объекта пятого блока, второй уничтожает объект из первого блока. Ну как-то так. В итоге количество вызовов деструктора совпадает с вызовами конструкторов.
А теперь попробуйте закомментировать конструктор копирования в определении класса и запустите программу. Вы увидите, что конструктор сработает только дважды, а деструктор, рад стараться, отработает пять раз. И если бы он освобождал память — это неизбежно привело бы к ошибке программы.
Комментарии
Maks Maksuta
Статья маленька но очень понятная. Целый час копаюсь в гугле, пытаясь найти что-то адекватное про конструктор окпирования. Это первый сайт где все коротко и понятно!
Спасибо.
blablabla
ничего непонятно