Механизм копирования объектов в C++ делится на поверхностное копирование (shallow copy) и глубокое копирование (deep copy). Разница особенно важна для классов с динамически выделяемой памятью.
В C++ многие структуры данных работают с динамической памятью (new/delete). По умолчанию компилятор генерирует конструктор копирования и оператор присваивания, совершающий копирование побайтово (shallow copy). Это быстро, но опасно, если объект управляет внешними ресурсами.
Shallow copy копирует только адреса динамически выделенных ресурсов. При удалении одного объекта память будет освобождена, а другой экземпляр останется с "висячим" (dangling) указателем. В результате возникают double delete, утечки памяти и сбои.
Глубокое копирование задействует явное создание копии всех динамических ресурсов. Для этого в классе необходимо самостоятельно реализовать конструктор копирования и оператор присваивания, чтобы обеспечивать копию каждого элемента.
Пример кода для класса с массивом:
class DynArray { int* data; size_t size; public: DynArray(size_t n) : size(n), data(new int[n]) {} ~DynArray() { delete[] data; } // Deep copy constructor DynArray(const DynArray& other) : size(other.size), data(new int[other.size]) { for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } // Deep copy assignment DynArray& operator=(const DynArray& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; for (size_t i = 0; i < size; ++i) data[i] = other.data[i]; } return *this; } };
Ключевые особенности:
Компилятор всегда корректно генерирует copy constructor и assignment operator, верно?
Ответ:
Неверно. Для классов с динамическими ресурсами копирование по умолчанию некорректно: оба объекта будут владеть одним и тем же ресурсом. Deep copy необходимо реализовывать явно при владении внешними ресурсами.
Нужно ли реализовывать деструктор, если написан only deep copy constructor/assignment?
Ответ:
Да, иначе возникнет утечка памяти: если освобождать память в пользовательском конструкторе копирования, но не реализовать деструктор, память никем не будет освобождаться при разрушении объектов.
Может ли std::vector хранить указатели и почему при его копировании возможны утечки?
Ответ:
Да, std::vector спокойно хранит указатели. При копировании такого std::vector копируются сами указатели, а не объекты, на которые они указывают. Это shallow copy: если нужно deep copy всего содержимого, потребуется вручную копировать каждый объект и размещать их в памяти независимо.
Пример:
std::vector<int*> v1; v1.push_back(new int(42)); std::vector<int*> v2 = v1; // Копируются указатели, а не *int
Программист реализует класс-обёртку массива, не переопределяя конструктор копирования/оператор присваивания. В результате оба объекта владеют одной памятью, destroy одного приводит к краху при доступе ко второму.
Плюсы:
Минусы:
Разработчик реализует глубокое копирование: копируются содержимое массива, есть свой деструктор и оператор присваивания с защитой от self-assignment.
Плюсы:
Минусы: