Question bank — RAII — resource acquisition is initialization — why it's the key idiom
Two pictures to hold in your head
Before the traps, burn these two images in — almost every question below is really about one of them.
Picture 1 — the stack destroys in reverse (LIFO). Objects are built bottom-up as a scope runs; when the scope ends for any reason, they are torn down top-down. RAII rides on this guaranteed teardown.

Picture 2 — one owner vs. shared ownership. A unique_ptr is the sole owner: exactly one destructor frees the resource. A shared_ptr keeps a reference count in a side "control block": only the last owner to die runs delete. This is why one is copyable-safe and the other forbids copying.

True or false — justify
Every item is "True or False, and say why". A bare yes/no scores zero — the justification is the whole point.
RAII's name accurately describes what makes it powerful.
If a function has no throw statements, RAII gives you no benefit.
return/break paths each skip manual cleanup — RAII covers all exits, not just exceptions.A destructor is guaranteed to run when a local object's scope ends by normal return, break, or a thrown exception.
std::exit(), std::abort(), or std::terminate() — those skip local destructors (see Edge cases). See Stack Unwinding and Exceptions.Wrapping a raw new in a std::unique_ptr can cause a double-free if you copy the pointer.
unique_ptr (the single-owner smart pointer in Picture 2) is non-copyable by design; the compiler rejects a copy, so the double-free is structurally impossible. See Smart Pointers - unique_ptr shared_ptr.Two shared_ptrs to the same object will double-free when both go out of scope.
shared_ptr keeps a reference count; each copy bumps it up, each destruction bumps it down, and only the last owner (count → 0) runs delete — exactly the mechanism in Picture 2. See Smart Pointers - unique_ptr shared_ptr.std::vector<int> v(100) and int* p = new int[100] are equally leak-safe.
vector's destructor frees the buffer automatically on any scope exit; the raw new[] needs a matching delete[] that an exception or early return can skip.Local objects are destroyed in the same order they were constructed.
Moving from an RAII object and then letting the source be destroyed frees the resource twice.
nullptr; the source's destructor then does nothing, keeping "exactly one owner frees" true. A move transfers ownership rather than copying it; see Move Semantics.Making a destructor noexcept is just a style preference.
std::terminate — so noexcept (a promise not to throw) on destructors is a correctness requirement.Spot the error
Each line describes flawed code or reasoning; the reveal names the bug and the fix.
Widget* w = new Widget(); use(w); delete w; — what breaks it?
use(w) throws, delete w is skipped → leak. Fix: auto w = std::make_unique<Widget>(); so the destructor frees it on any exit.A class holds a FILE* and relies on the compiler-generated copy constructor — what goes wrong?
fclose. Fix via the Rule of Five (control every copy/move/destroy moment); see Rule of Three Five Zero.m.lock(); risky(); m.unlock(); inside a function — the trap?
risky() throws, unlock() never runs → the mutex stays locked forever → deadlock. Fix: std::lock_guard<std::mutex> g(m);, a guard whose destructor unlocks automatically. See std::lock_guard and Mutexes.An RAII owner's destructor does throw std::runtime_error(...) on a failed cleanup — why forbidden?
std::terminate. Fix: swallow or log the error, mark the destructor noexcept.A function returns const char* pointing into a local std::string, then the string goes out of scope. What's the fault?
string's buffer is freed at scope end, so the returned pointer dangles → use-after-free. Fix: return by value or transfer ownership.std::unique_ptr<int> a = b; where b is another unique_ptr — will it compile?
std::unique_ptr<int> a = std::move(b);, which transfers ownership and empties b. See Move Semantics.Someone writes a Rule-of-Five class but forgets to release the old resource in copy-assignment.
Why questions
Why is putting a resource inside a stack object the whole trick, rather than being careful with delete?
return/throw, which fails at scale.Why does move semantics need to null the source pointer, not just copy it?
Why does shared_ptr need a separate control block instead of just a raw count in the object?
Why is the Rule of Five about controlling "every moment the resource could be created, copied, or destroyed"?
Why does RAII give you the "basic" and even "strong" exception-safety guarantee almost for free?
Why prefer std::lock_guard over manually pairing lock()/unlock()?
Why is "Rule of Zero" often the best RAII advice?
Edge cases
If a constructor throws after acquiring one resource but before finishing, does the destructor run to clean it up?
What happens to RAII cleanup if you call std::abort(), std::exit(), or the process crashes?
Does a normal thrown exception (that gets caught) skip destructors like std::exit() does?
std::terminate, or an explicit std::abort()/std::exit(), skips them.A unique_ptr is default-constructed and never assigned a resource. Does its destructor cause trouble?
nullptr, and delete nullptr is a well-defined no-op, so the empty state is perfectly safe.An exception is thrown while the stack is already unwinding from a prior exception — what does C++ do?
std::terminate; you can't have two exceptions propagating at once, which is precisely why destructors must be noexcept. See Stack Unwinding and Exceptions.A shared_ptr cycle (two objects pointing at each other) goes out of scope. Are they freed?
weak_ptr. See Smart Pointers - unique_ptr shared_ptr.You move-construct from an RAII object and then use the moved-from source. Is that undefined behaviour?
A global/static RAII object owns a resource — when does its destructor run?
Recall One-line summary to carry away
RAII works because the destructor of a stack object is guaranteed to run whenever the scope unwinds normally; every trap above is either "someone bypassed that unwinding (e.g. std::exit/abort)" or "someone broke the single-owner invariant."