Advanced⏱️ 10 min📘 Topic 20 of 32

📑 Copy Constructor and the Rule of Three (and Five) in C++

Master the copy constructor, copy assignment, the Rule of Three, Rule of Five and Rule of Zero in C++. Shallow vs deep copy, move semantics — with examples and interview Q&A.

When a class manages a resource (raw pointer, file handle, socket), the compiler-generated default copy may be WRONG. You need to control how copies are made.

🔄 The big 3 (or 5)

  • Destructor~T()
  • Copy constructorT(const T&)
  • Copy assignmentT& operator=(const T&)
  • Move constructor (C++11+) — T(T&&)
  • Move assignment (C++11+) — T& operator=(T&&)

📏 The rules

  • Rule of Three — if you define one of destructor / copy ctor / copy assign, define all three.
  • Rule of Five — same plus move ctor + move assign for performance.
  • Rule of Zero — design so you don't need any: use STL containers and smart pointers; the defaults work.

🌊 Shallow vs deep copy

class Buf {
  int* data;
  size_t size;
public:
  // Deep copy constructor
  Buf(const Buf& other) : size(other.size), data(new int[other.size]) {
    std::copy(other.data, other.data + size, data);
  }
  ~Buf() { delete[] data; }
};

💡 Modern advice

Use std::vector, std::string, std::unique_ptr as members — let the compiler write the right copy/move for you (Rule of Zero).

💻 Code Examples

Shallow copy disaster (without rule of 3)

class Bad {
  int* p;
public:
  Bad() : p(new int(5)) {}
  ~Bad() { delete p; }
  // implicit copy ctor copies POINTER — both objects share p!
};
Bad a, b = a;  // double-delete when both destructors run
Output:
Crash on exit.

Rule of Zero — let STL handle it

class Good {
  std::vector<int> data;     // STL container handles copy/move
  std::string name;
public:
  Good(std::string n, std::vector<int> d) : data(std::move(d)), name(std::move(n)) {}
  // no destructor, no copy ctor, no assign needed — compiler-generated is correct
};
Output:
Clean, safe, no manual rules needed.

⚠️ Common Mistakes

  • Defining a destructor but not the copy ctor or assignment — leaks or double-frees.
  • Forgetting to handle self-assignment: `a = a` — `if (&other == this) return *this;`.
  • Not marking move constructor `noexcept` — STL containers fall back to copying for safety.
  • Implementing the Rule of Three manually when STL containers would do it for free.

🎯 Interview Questions

Real questions asked at top product and service-based companies.

Q1.What is the Rule of Three?Intermediate
If you need to manually define ANY of the destructor, copy constructor, or copy assignment operator, you almost certainly need to define ALL THREE. The compiler-generated versions don't handle resources correctly.
Q2.What's the difference between shallow and deep copy?Intermediate
Shallow copy: copies the pointer/handle (both objects share the same underlying data). Deep copy: allocates new memory and copies the data, so each object owns independent storage.
Q3.What is the Rule of Zero?Intermediate
Design classes so the compiler's defaults work — use STL containers, smart pointers, and standard types as members instead of raw pointers. Result: no destructor, no copy ctor, no assignment needed.
Q4.What does the Rule of Five add?Advanced
Move constructor and move assignment operator (C++11+) — for transferring ownership of resources without copying. Doubles performance for large objects in modern code.
Q5.Why should move operations be noexcept?Advanced
STL containers (like vector) only use move operations during reallocation if they're noexcept. Otherwise they fall back to copying for exception safety, killing performance. Always mark move ctor/assign noexcept when possible.

🧠 Quick Summary

  • Compiler generates default destructor/copy/move — sometimes WRONG for resources.
  • Rule of Three: destructor + copy ctor + copy assign go together.
  • Rule of Five: add move ctor + move assign.
  • Rule of Zero: use STL members so no manual rules needed.
  • Mark move operations `noexcept` for STL optimization.