ProgrammingC++ Developer

Explain the difference between shallow copy and deep copy for classes with dynamic memory. When and why is a deep copy constructor necessary? How to manually implement deep copy?

Pass interviews with Hintsage AI assistant

Answer.

Background

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.

Problem

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.

Solution

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:

  • Required for classes managing external resources
  • Custom copy constructor and operator= implementations are necessary
  • Memory leaks can occur without a proper implementation of the Rule of Three

Tricky Questions.

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) { ... }

Common Mistakes and Anti-Patterns

  • One of the Rule of Three methods (copy constructor, operator=, destructor) is not implemented
  • Self-assignment is not checked
  • Memory leak due to forgotten delete[]
  • Double free if pointers turned out to be shared

Real-Life Example

Negative Case

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:

  • Easy to write code ("copy works" at first glance)

Cons:

  • Program crash, unpredictable bugs
  • Memory leaks or double-free

Positive Case

A developer correctly implements the copy constructor, assignment operator, and destructor. Each object owns its memory.

Pros:

  • Safety during copying and destruction
  • No leaks

Cons:

  • With a large number of copies, copying overhead increases