14.10  Конструктор копирующий

Конструктором копирования (в англоязычной литературе используется термин copyconstructor) называется специальный конструктор в языке программирования C++, применяемый для создания нового объекта как копии уже существующего. Такой конструктор принимает как минимум один аргумент – ссылку на копируемый объект.

Обычно компилятор автоматически создает конструктор копирования для каждого класса (они известны как неявные конструкторы копирования, т.е. конструкторы копирования, заданные неявным образом).

Рассмотрим пример неявного конструктора копирования.

Пример 3

#include <iostream>

class Person

{

public:

int age;

Person(int age) : age(age) { }

};

 

int main()

{

Person timmy(10);

Person sally(15);

Person timmy_clone = timmy;

std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age <<  std::endl;

timmy.age = 23;

std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;

}

Результат работы программы представлен на рис. 14.1.

Рис. 14.1. Результат работы программы (пример 3)

Как и ожидалось, объект timmy скопировался в новый объект timmy_clone. При изменении возраста (age) timmy, у timmy_clone возраст не менялся. Это потому, что они являются полностью независимыми объектами.

Компилятор сгенерировал для нас неявный конструктор копирования, который может быть записан примерно так:

Person(Person const& copy) : age(copy.age) { }

Рассмотрим другой пример, в котором создан очень простой класс динамических массивов.

Пример 4

#include "stdafx.h"

#include <iostream>

class Array

{

public:

int size;

int* data;

Array(int size): size(size), data(new int[size]) { }

~Array()

{

delete[ ] data;

}

};

int main()

{

Array first(20);

first.data[0] = 25;

{

Array copy = first;

std::cout << first.data[0] << " " << copy.data[0] << std::endl;

}    // (1)

first.data[0] = 10;    // (2)

return 0;

}

Хотя мы не указывали конструктор копирования, компилятор сгенерировал его. Генерируемый конструктор выглядит примерно так:

Array(Array const& copy)  : size(copy.size), data(copy.data) { }

Проблема, связанная с этим конструктором, заключается в том, что он выполняет простое копирование указателя data. Он только копирует адрес, а не сами данные. И когда программа доходит до строчки (1), вызывается деструктор copy (объекты в стеке уничтожаются автоматически при достижении их границ). Как видно, деструктор Array удаляет массив data, поэтому когда он удаляет данные copy, он также удаляет данные first. Строка (2) теперь получает неправильные данные и записывает их! Это и приводит к знаменитой ошибке сегментации (segmentation fault) (рис. 14.2).

Рис. 14.2. Сообщение пользователю об ошибке сегментации

В этих случаях программист должен создать свой конструктор копирования, который назван явным конструктором копирования. В этом случае компилятор неявный конструктор создавать не будет. Явный конструктор копирования выполняет глубокое копирование:

Array(Array const& copy) : size(copy.size), data(new int[copy.size])

{

std::copy(copy.data, copy.data + copy.size, data);

 }

Здесь мы создаем новый массив int и копируем содержимое в него. Теперь, деструктор copy только удалит его данные и не тронет данные first. Строка (2) больше не вызовет ошибку сегментации.