Rule of Zero — prefer compiler-generated specials
WHAT are we even talking about?
This is the modern counterpart to the older Rule of Three ("if you write one of destructor/copy-ctor/copy-assign, write all three") and Rule of Five (add the two move ops). Rule of Zero says: the best version of the Rule of Three/Five is the one you never have to apply.
WHY does this rule exist? (first-principles derivation)
Let's derive the need for special members from scratch.
Step 1 — What does copying mean?
Copying a value should produce an independent object that compares equal but does not share mutable state. For a plain int, copying bytes is enough. So the compiler's default = memberwise copy is correct.
Why this step? If memberwise copy is always correct, you never need a hand-written copy ctor.
Step 2 — When does memberwise copy break?
It breaks only when a member is a raw owning resource — a raw T* from new. Then memberwise copy duplicates the pointer, not the pointee. Two objects now point at one buffer.
struct Bad {
int* p; // raw owning pointer
Bad(int n) : p(new int[n]) {}
~Bad() { delete[] p; } // <-- forced to write destructor
};Why this step? The moment you delete[] p in a destructor, the default copy becomes a double-free bug. That single hand-written destructor forces you into the Rule of Three.
Step 3 — The cascade. Writing the destructor means the default copy is now wrong → you must write copy ctor + copy assign → and (post-C++11) the compiler stops generating move operations when you declare a destructor/copy → so for performance you must write moves too → Rule of Five. Five tricky, exception-sensitive functions, all to babysit one raw pointer.
struct Good {
std::vector<int> p; // owns memory, copies deeply, moves cheaply
Good(int n) : p(n) {}
// no destructor, no copy, no move — all correct, all free
};Why this works: std::vector already has a correct destructor (frees), correct copy (deep), and correct move (steals). Memberwise generation simply calls vector's correct versions. Correctness is composed, not rewritten.
HOW to apply it (decision recipe)
| Member kind | Owns a resource? | Needs hand-written specials? |
|---|---|---|
int, double, enum |
no | no |
std::string, std::vector |
yes, self-managed | no (Rule of Zero) |
std::unique_ptr<T> |
yes, self-managed | no |
std::shared_ptr<T> |
yes, self-managed | no |
raw T* = new T |
yes, manual | yes (Rule of Five) — or wrap it! |
FILE*, OS handle |
yes, manual | wrap in a custom RAII class |

Worked examples
Common mistakes (steel-manned)
Recall Feynman: explain it to a 12-year-old
Imagine each Lego brick already knows how to make a perfect copy of itself and how to clean itself up. If you build a spaceship out of only these smart bricks, then when you want to copy the whole spaceship, you don't write any instructions — you just say "everybody, copy yourselves," and the spaceship copies perfectly. You only have to write copy instructions if you used a dumb brick (like a sticky note that points to a shared box). The trick: never use dumb bricks directly. Wrap the dumb brick inside one smart box once, and use the smart box everywhere.
Active-recall flashcards
What are the five special member functions (Big Five)?
State the Rule of Zero.
Why does writing one destructor force the Rule of Five?
What side effect does declaring a destructor (even =default) have on move operations?
A class has only a std::unique_ptr member and no user-written specials. Is it copyable?
When MUST you still write special members?
new, FILE*, OS handle) with no self-managing wrapper.What's the recommended fix when a resource has no RAII wrapper?
Rule of Zero vs Rule of Three/Five — relationship?
Connections
- RAII — Resource Acquisition Is Initialization
- Rule of Three and Rule of Five
- std::unique_ptr and ownership semantics
- std::shared_ptr — shared ownership
- Move semantics and rvalue references
- noexcept and move operations
- Copy-and-swap idiom
- Single Responsibility Principle
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho yaar, C++ me har class ke paas paanch "special functions" hote hain — destructor, copy constructor, copy assignment, aur do move wale. Rule of Zero ka simple funda hai: agar tumhari class ke saare members khud hi apna copy, move aur cleanup sambhal lete hain (jaise std::string, std::vector, std::unique_ptr), toh tumhe in paanch me se ek bhi function likhne ki zaroorat NAHI hai. Compiler khud sahi version bana dega, bilkul free me. Tum likho zero, kaam ho jaaye perfect.
Problem kab aati hai? Jab tum raw pointer rakhte ho jaise int* p = new int[n]. Tab tumhe destructor likhna padta hai delete[] ke liye, aur jaise hi destructor likha — default copy galat ho jaata hai (double free crash), phir copy likho, phir compiler move band kar deta hai, phir move bhi likho. Ek chhoti si galti se poora Rule of Five ka jhamela. Isliye raw pointer ko seedha use mat karo — usko vector ya unique_ptr me daal do, aur cascade khatam.
Ek important trap yaad rakhna: agar tum sirf debug ke liye bhi destructor add kar do (chahe = default hi kyun na ho), toh compiler tumhare move functions silently band kar deta hai, aur class chupke se copy karne lagti hai — performance gir jaati hai. Isliye bina zaroorat ke koi bhi special function mat declare karo.
Aur jab koi resource ka RAII wrapper available na ho (jaise FILE* ya OS handle), tab ek chhoti si RAII class banao jisme ye paanch functions ho, aur baaki saari classes us wrapper ko use karke Rule of Zero follow karein. Matlab: ownership ka dangerous kaam ek hi jagah, baaki sab clean. Yahi modern, safe, kam-bug wala C++ hai.