В C++ изначально при копировании объектов используется механизм memberwise copying: для каждого члена объекта вызывается собственная операция копирования. Для встроенных типов это безопасно, но для динамических ресурсов возникает проблема: копируются только указатели, но не сами данные.
Если объект содержит указатель на выделенную в heap память, то после копирования двух объектов они будут указывать на одну и ту же область памяти. Тогда при уничтожении одного объекта память освободится и указатель второго станет невалидным ("дикий" указатель). Это приводит к ошибкам времени выполнения и утечкам.
Чтобы копия была независимой, необходим deep copy — побайтовое копирование и выделение собственного буфера. Это реализуется путём написания пользовательского конструктора копирования и оператора присваивания.
Пример кода:
class MyString { char* data; public: MyString(const char* s) { data = new char[strlen(s)+1]; strcpy(data, s); } // Deep copy constructor MyString(const MyString& src) { data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } // Deep copy assignment MyString& operator=(const MyString& src) { if (this != &src) { delete[] data; data = new char[strlen(src.data) + 1]; strcpy(data, src.data); } return *this; } ~MyString() { delete[] data; } };
Ключевые особенности:
Зачем нужен пользовательский деструктор, если класс содержит только указатель, но память не выделяется?
Деструктор необходим только если вы явно выделяли память (или иной ресурс) внутри класса. Если указатель не выделяет память, дефолтного деструктора достаточно.
Что произойдет, если не реализовать operator= в классе с динамической памятью, но конструктор копирования объявлен?
Если вы определили конструктор копирования вручную, compiler не реализует operator= автоматически; будет либо объявлен неявно, либо компилятор выдаст ошибку/предупреждение (зависит от стандарта). Это приведёт к плохо определённому поведению при присваивании: произойдёт memberwise copy и возникнет double free или утечка.
Пример кода:
MyString a("hi"); MyString b = a; // Ok: ваш копирующий конструктор MyString c("bye"); c = a; // Problem! Если operator= не реализован вручную, будет shallow copy
Чем опасно присваивание самому себе при ручной реализации operator=?
Если делить ресурсы, не проверяя this!=&rhs, то при присваивании самому себе будет выполнено delete[] data, а потом копирование уже уничтоженного массива, что приводит к segfault. Самозащита: всегда проверяйте self-assignment.
if (this != &rhs) { ... }
Разработчик копирует объект класса со встроенным указателем, не реализуя deep copy. После копирования несколько объектов разделяют одну область памяти. Два деструктора вызывают double delete, падает программа.
Плюсы:
Минусы:
Разработчик грамотно реализует конструктор копирования, оператор присваивания и деструктор. Каждый объект владеет своей памятью.
Плюсы:
Минусы: