In C++, the default mechanism for copying objects is memberwise copying: for each member of the object, its copy operation is called. This is safe for built-in types, but for dynamic resources, there is a problem: only the pointers are copied, not the actual data.
If an object contains a pointer to dynamically allocated memory on the heap, after copying two objects, they will point to the same memory area. Then, when one object is destroyed, the memory will be freed, and the pointer of the second object will become invalid (a "wild" pointer). This leads to runtime errors and memory leaks.
To ensure that the copy is independent, a deep copy is necessary — a byte-by-byte copy and allocation of its own buffer. This is implemented by writing a custom copy constructor and assignment operator.
Example code:
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; } };
Key features:
Why is a custom destructor needed if the class only contains a pointer, but no memory is allocated?
A destructor is necessary only if you explicitly allocated memory (or another resource) within the class. If the pointer does not allocate memory, the default destructor is sufficient.
What happens if operator= is not implemented in a class with dynamic memory, but the copy constructor is declared?
If you've manually defined a copy constructor, the compiler will not automatically implement operator=; it will either be implicitly declared or the compiler will issue an error/warning (depends on the standard). This will lead to poorly defined behavior during assignment: a memberwise copy will occur, resulting in double free or leak.
Example code:
MyString a("hi"); MyString b = a; // Ok: your copy constructor MyString c("bye"); c = a; // Problem! If operator= is not manually implemented, it will be a shallow copy
What are the dangers of self-assignment when manually implementing operator=?
If resources are shared without checking this!=&rhs, then on self-assignment, delete[] data will be executed, and then copying from a destroyed array will occur, leading to a segfault. Self-protection: always check for self-assignment.
if (this != &rhs) { ... }
A developer copies an object of a class with a built-in pointer without implementing deep copy. After copying, multiple objects share the same memory area. Two destructors call double delete, crashing the program.
Pros:
Cons:
A developer correctly implements the copy constructor, assignment operator, and destructor. Each object owns its memory.
Pros:
Cons: