Visual walkthrough — RAII — resource acquisition is initialization — why it's the key idiom
Step 1 — What is "the stack"? (draw it before naming it)
WHAT. When your program calls a function, the computer sets aside a little shelf of memory for that function's local variables (the ones you declare with a name inside the function). We call this shelf a stack frame. Frames pile up: main calls f, f calls g — three shelves stacked on top of each other.
WHY a stack (and not a random heap of boxes)? Because function calls nest perfectly: the last function you entered is always the first one you leave. "Last in, first out" — the exact shape of a stack of plates. So the memory follows the same last-in-first-out rule. This ordering is the whole secret we will exploit.
PICTURE. Each function that is currently running owns one horizontal shelf. New calls push a shelf on top; returning pops the top shelf off.

Step 2 — Construction and destruction are two guaranteed moments
WHAT. Every object in C++ has exactly two special moments in its life:
- its constructor runs the instant it is born (when you declare it),
- its destructor runs the instant it dies (when its block ends).
We write a destructor as ~ClassName(). See Constructors and Destructors for the mechanics.
WHY does this help us? Because the destructor call is not optional and not something you schedule — the language schedules it. If we can make "release the resource" the content of the destructor, then releasing becomes as guaranteed as the object's death.
PICTURE. A single object drawn as a lifeline: a green dot where the constructor fires, a red dot where the destructor fires. The resource (a file, a lock) is grabbed at green and dropped at red.

Step 3 — The naive manual version, and where it cracks
WHAT. First let's do it by hand, the way you would in an older language: acquire on one line, release on another.
void f() {
int* p = new int[100]; // acquire (line grabs 100 ints of heap memory)
might_throw(); // work
delete[] p; // release (gives the memory back)
}WHY does this crack? The release line is only reached if control flows straight through. But three things can jump over it:
- an early
return; - a
break;out of a loop - a thrown exception inside
might_throw().
PICTURE. The straight arrow of control flow is drawn in blue. Three red arrows show the escape routes that leap over delete[], leaving the memory grabbed but never released — a leak.

Step 4 — Stack unwinding: the guarantee we will ride
WHAT. When a function exits for any reason at all — normal return, break, or a thrown exception passing through — C++ walks back down that function's stack frame and runs the destructor of every local object it finds, in reverse order of construction (last born, first to die). This walk-back is called stack unwinding. See Stack Unwinding and Exceptions.
WHY reverse order? Because a later object may have been built using an earlier one. If b borrowed something that a owns, we must destroy b (the borrower) before a (the owner), or b's destructor would touch a resource already gone. Last-in-first-out keeps every dependency valid right up to the moment it dies.
PICTURE. Two objects a then b are constructed left-to-right (green). When the block ends, arrows show the destructors firing right-to-left: b first, then a.

Step 5 — Put the resource inside a local object
WHAT. Now we glue Steps 2 and 4 together. Instead of a bare new, we make the resource live inside a local object whose destructor releases it.
void f() {
std::vector<int> v(100); // constructor acquires 100 ints
might_throw(); // work
} // v's destructor releases — on EVERY exit pathWHY this wins. v is a local object on the stack. By Step 4, its destructor runs no matter how f exits. By Step 2, that destructor is the release. So the leak of Step 3 cannot happen — not because we were careful, but because the language's unwinding does the work.
PICTURE. Same three escape arrows as Step 3, but now every one of them is forced to pass through v's red destructor dot before leaving. There is no path out that skips release.

Step 6 — The double-free trap and the "one owner" rule
WHAT. There is one way RAII can still bite: if two objects think they own the same resource, both destructors run and the resource is released twice — a double free, which corrupts memory or crashes.
How do we get two owners? By copying an owning object with the compiler's default copy, which just duplicates the raw pointer value:
FileHandle a("x.txt");
FileHandle b = a; // default copy: b.f == a.f (same FILE*)
// both destructors call fclose on the SAME file → double freeWHY it happens. The default copy copies the number in the pointer, not the thing it points at. Now two objects hold identical pointers; each believes it owns the file.
PICTURE. Two boxes a and b both drawing arrows to the same file. Two red destructor dots both try to close it — the second close is on already-freed memory (drawn as a red cross).

Step 7 — Move: "steal, then null" keeps exactly one owner
WHAT. Sometimes we want to hand a resource from one object to another cheaply — a move. See Move Semantics. The move constructor takes the pointer from the source and sets the source's pointer to nullptr:
FileHandle(FileHandle&& o) noexcept // move constructor
: f(o.f) // steal: the new object takes the pointer
{ o.f = nullptr; }// null: source now owns nothingWHY null the source? Because the destructor is written ~FileHandle() { if (f) fclose(f); }. After the steal, the source's f is nullptr, so its if (f) is false and it closes nothing. Result: exactly one owner ever frees the resource. That single-owner invariant is what makes RAII safe under transfer.
PICTURE. The pointer arrow is redrawn from source to destination; the source's slot becomes nullptr (greyed out), so only one live red arrow to the file remains.

The one-picture summary
Every escape route from a scope — normal, break, or thrown exception — is funnelled through the reverse-order destructor chain of the local objects. Put the resource inside those objects, keep exactly one owner (delete copy or steal-and-null on move), and cleanup becomes structurally unskippable.

Recall Feynman retelling — say it in plain words
The computer keeps function calls on a stack of shelves; the last shelf you added is the first you take away. Every named object on a shelf gets two guaranteed events: a birth (constructor) and a death (destructor). The trick of RAII is to grab a resource at birth and give it back at death. Now here's the magic: when a function leaves — even because an exception is flying through it — the language walks the shelf backwards and fires every object's death event. So the "give it back" runs no matter what. The only remaining danger is two objects thinking they own the same thing, which would give it back twice. We stop that by forbidding careless copies and, when we move ownership, stealing the pointer and nulling the source so only one owner is ever left to release. That's the entire idea: lifetime of resource = lifetime of a stack object, and the stack never forgets to clean up.
Recall Quick checks
Which order do destructors run in on scope exit? ::: Reverse of construction — last built, first destroyed (LIFO). Why does an exception NOT cause a leak with RAII? ::: Because unwinding runs the local object's destructor on the exception path, and the destructor is the release. Why does a move set the source pointer to null? ::: So the source's destructor releases nothing, leaving exactly one owner to avoid a double free. Why must a destructor be noexcept? ::: A throw during unwinding of another exception calls std::terminate.