5.2.11C++ Programming

Rule of Zero — prefer compiler-generated specials

2,279 words10 min readdifficulty · medium6 backlinks

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
Figure — Rule of Zero — prefer compiler-generated specials

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)?
Destructor, copy ctor, copy assignment, move ctor, move assignment.
State the Rule of Zero.
Design classes so members are self-managing (RAII) types; then write zero special member functions and let the compiler generate them.
Why does writing one destructor force the Rule of Five?
A hand destructor implies manual resource ownership → default copy becomes wrong (double free) → must write copy ops → declaring those suppresses moves → must write moves too.
What side effect does declaring a destructor (even =default) have on move operations?
It suppresses the compiler-generated move constructor and move assignment, so the class silently falls back to copying.
A class has only a std::unique_ptr member and no user-written specials. Is it copyable?
No — copy is implicitly deleted (unique_ptr isn't copyable); it is move-only, which is automatically correct.
When MUST you still write special members?
Only when the class directly owns a non-RAII resource (raw new, FILE*, OS handle) with no self-managing wrapper.
What's the recommended fix when a resource has no RAII wrapper?
Write one tiny RAII class wrapping that resource (with its own specials), then every other class uses Rule of Zero.
Rule of Zero vs Rule of Three/Five — relationship?
Three/Five say "if you write one resource-managing special, write all of them"; Rule of Zero says the best application is to need none at all by composing RAII members.

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

prescribes

self-manage copy move destroy

compiler synthesizes

forces

default copy becomes double-free

add move ops

suppresses moves

replaces

collapses cascade to

modernizes

modernizes

Rule of Zero

Write zero special members

RAII member types

Correct Big Five

Raw owning pointer

Hand-written destructor

Rule of Three

Rule of Five

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.

Go deeper — visual, from zero

Test yourself — C++ Programming

Connections