ПрограммированиеC++ разработчик

Объясните разницу между shallow copy и deep copy на примере контейнера с динамической памятью. Как правильно реализовать глубокое копирование?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Shallow copy (поверхностное копирование) копирует только значения членов класса, включая указатели — но не копирует данные по этим указателям. Deep copy (глубокое копирование) создает новые копии данных, находящихся по указателям, что предотвращает совместное владение памятью и двойное освобождение.

Пример:

class Buffer { public: Buffer(size_t size) : size(size), data(new int[size]) {} // Shallow copy (неправильно) Buffer(const Buffer& other) : size(other.size), data(other.data) {} // Deep copy (правильно) Buffer& operator=(const Buffer& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } ~Buffer() { delete[] data; } private: size_t size; int* data; };

Вопрос с подвохом.

Если класс содержит указатель на динамический массив, почему не достаточно копи-пастить copy constructor и operator= по умолчанию?

Правильный ответ:

Копирование по умолчанию реализует поверхностное копирование. Два объекта будут указывать на один и тот же массив, и при уничтожении будет дважды попытка освободить одну и ту же память (double free), что приведет к краху.

Примеры реальных ошибок из-за незнания тонкостей темы.


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


История В студенческом проекте использовался контейнер Field, содержащий указатель на динамически выделенный массив клеток. При изменении размера массива копирующий конструктор копировал только указатель, и оба контейнера работали с одной областью памяти, что приводило к неконсистентным данным и труднонаходимым багам.


История В старом C++-коде графического движка был реализован класс Texture без своего деструктора и оператора присваивания, что приводило к memory leak — утечкам памяти при копировании и присваивании временных объектов текстур.