5.2.7C++ Programming

Destructor — RAII principle

1,855 words8 min readdifficulty · medium

WHAT is a destructor?

When does "lifetime end"? Three common cases:

  1. A local (stack) object goes out of scope (reaches } or a return/throw).
  2. A delete is called on a heap object (delete p;).
  3. A program/main ends, destroying static/global objects.

WHY does RAII exist? (Steel-man the manual way first)

The "manual" approach feels fine:

void f() {
    int* p = new int[100];   // acquire
    // ... use p ...
    delete[] p;              // release
}

It feels safe because the delete[] is right there. But what if // use p throws an exception? Control jumps straight out of f, skipping delete[] p → memory leak. What if someone adds an early return in the middle six months later? Same leak. Manual cleanup is correct only on the happy path.


HOW to build an RAII wrapper (derivation from scratch)

Goal: make raw memory safe. Step by step, asking why at each step.

class IntArray {
    int* data;            // the owned resource
    size_t n;
public:
    IntArray(size_t size)              // ACQUIRE in constructor
        : data(new int[size]), n(size) {}   // Why? RAII: acquisition = initialization
 
    ~IntArray() { delete[] data; }    // RELEASE in destructor
                                      // Why? guaranteed to run on every exit path
 
    int& operator[](size_t i) { return data[i]; }   // Why? use it like an array
    size_t size() const { return n; }
};
 
void f() {
    IntArray a(100);   // acquire
    a[0] = 42;
    // ... even if this throws, ~IntArray runs and frees data ...
}                      // destructor fires here — automatic delete[]

Why this works on the exception path: during stack unwinding, every fully-constructed local object on the stack has its destructor called. a is one of them ⇒ no leak.

Figure — Destructor — RAII principle

Order of destruction (the LIFO rule)

Why reverse? Later objects may depend on earlier ones. Tearing down in LIFO order guarantees a dependency is still alive while whatever uses it is being destroyed.


Worked examples


Recall Feynman: explain to a 12-year-old

Imagine you borrow a library book (the resource). Instead of trusting yourself to return it, you make a magic backpack: the moment you put the book in, a timer starts, and when you leave the building the backpack automatically returns the book — even if you run out in a panic (an exception!). The backpack is the C++ object, putting the book in is the constructor, and the auto-return is the destructor. You never forget, because the building's exit (end of scope) does the returning for you.


Flashcards

What does RAII stand for and what is its core idea?
Resource Acquisition Is Initialization — tie a resource's lifetime to an object: acquire in the constructor, release in the destructor, so cleanup is automatic and exception-safe.
When exactly does a destructor run?
Automatically when an object's lifetime ends: local goes out of scope, delete on a heap object, or program end for statics/globals; also during stack unwinding from an exception.
Why is RAII safer than manual new/delete?
Destructors are guaranteed to run on every exit path, including exceptions and early returns, so the resource is always released; manual cleanup is skipped if an exception or early return jumps over it.
What is the Rule of Three?
If you define a destructor, copy constructor, OR copy assignment, you almost certainly need all three — because owning a raw resource means default shallow copies cause double-free/aliasing bugs.
Why must a polymorphic base class have a virtual destructor?
So that delete base_ptr; invokes the derived destructor (then the base one); without virtual only the base destructor runs, leaking derived resources.
In what order are objects destroyed?
Reverse of construction (LIFO). For one object: derived body, then members in reverse declaration order, then base class.
Can a destructor take parameters or be overloaded?
No — it has no parameters, no return type, and there is exactly one per class.
What is the Rule of Zero?
Design classes that own no raw resources (use vector, unique_ptr, etc.) so you need no custom destructor/copy/move at all.

Connections

Concept Map

must be

fails on

causes

acquire =

release =

ties lifetime to

end triggers

runs during

guarantees

prevents

implements

Resource: memory, file, lock

Released eventually

Manual delete cleanup

Exception or early return

Resource leak

RAII principle

Constructor grabs resource

Destructor frees resource

Object lifetime

Stack unwinding

Unconditional cleanup

IntArray wrapper

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, RAII ka matlab hai Resource Acquisition Is Initialization — naam ghabrane wala hai par idea simple hai. Jab bhi tum koi resource lete ho (memory new se, file handle, ya mutex lock), us resource ko ek object ke andar "bandh" do. Object jab banta hai (constructor) tab resource le lo, aur object jab marta hai (destructor) tab resource chhod do. Faayda? C++ guarantee karta hai ki destructor har exit path par chalega — chahe normal return ho, ya beech mein exception aa jaaye. Manual delete likhne mein dikkat ye hai ki agar exception aa gaya to woh delete skip ho jaata hai aur memory leak ho jaati hai.

Socho ek library book example: tum khud yaad rakhne ke bharose mat raho. Ek magic backpack bana lo jo building se nikalte hi book khud return kar de. Backpack = object, book daalna = constructor, auto-return = destructor. Tum bhool bhi jaao to bhi kaam ho jaata hai. Isi tarah std::lock_guard constructor mein lock leta hai aur destructor mein unlock — agar doWork() exception phenke tab bhi unlock guaranteed, deadlock nahi hoga.

Ek important warning: Rule of Three. Agar tumne destructor likha hai jo delete[] karta hai, to default copy shallow copy karega — do objects same pointer ko point karenge aur dono destructor delete[] karenge => double free, crash. Isliye destructor likha to copy constructor aur copy assignment bhi sambhaalo, ya phir Rule of Zero follow karo — raw pointer manage hi mat karo, seedha std::vector ya std::unique_ptr use karo jo khud RAII hai.

Aur ek polymorphism wala point: agar base class pointer se delete karna hai, to base ka destructor virtual hona zaroori hai, warna sirf base ka destructor chalega aur derived ka resource leak ho jaayega. Yaad rakho: "Grab in the Greeting, Drop in the Goodbye" — constructor acquire, destructor release.

Go deeper — visual, from zero

Test yourself — C++ Programming

Connections