C++ RAII
Every resource (memory, file, lock, socket, GPU handle) lives in an
object whose constructor acquires and destructor releases. Once that
is true everywhere, early returns and exceptions cannot leak, and
cleanup order becomes deterministic scope order.
Method
- Aim for the rule of zero. Compose classes from members that
already manage themselves (
std::string, std::vector,
unique_ptr, lock_guard); then the compiler-generated special
members are correct and you write none of them. Only resource-
owning wrapper classes implement the rule of five, and each
such class does exactly one job: own that resource.
- Pick the pointer by ownership, not habit.
std::unique_ptr is the default for owned heap objects (zero
overhead, moves express transfer); std::shared_ptr only when
lifetime is genuinely shared by design (and weak_ptr breaks the
cycles); raw pointers and references mean "borrowed, non-owning,
never freed by me": document lifetime expectations at the API
(see rust-ownership for the same taxonomy enforced by a
compiler).
- Allocate with make functions, never naked new.
std::make_unique/make_shared tie allocation to the owner in
one expression; a new outside a smart-pointer constructor is a
review flag. delete appearing anywhere outside a custom
deleter means an ownership hole.
- Wrap every non-memory resource the same way. Files, mutexes
(
lock_guard/unique_lock, see deadlock-analysis), transactions
(commit/rollback in destructor via scope guards), C handles from
libraries (unique_ptr with custom deleter). If cleanup appears
in function bodies instead of destructors, error paths will
eventually skip it.
- Use move semantics as ownership transfer syntax. Return by
value (RVO/moves make it cheap), take sinks by value and move
into place, mark moved-from objects as valid-but-empty. Write
noexcept on moves (containers fall back to copies otherwise);
never throw from destructors: report or terminate, because a
throwing destructor during unwind ends the process.
- Let scope express lifetime in reviews. The audit question for
any function: every acquired thing owned by a named object whose
scope matches the intended lifetime? Ad hoc cleanup blocks,
goto-style flags, and paired init/teardown calls are the C
idiom this skill replaces (see c-memory-safety when C interop
forces it).
Boundaries
shared_ptr is not a garbage collector: cycles leak, atomic
refcounts cost, and "shared because deciding an owner was hard"
is deferred design debt.
- RAII manages resources, not object relationships; parent-child
back-pointers and observer lists still need lifetime thought
(weak_ptr, handles, or explicit unregistration).
- Interop with C APIs and exception-unsafe legacy code confines
RAII benefits to the wrapped boundary; wrap at the edge
immediately rather than letting raw handles roam (see
ffi-boundaries).
1---2name: cpp-raii3description: Manage every resource through RAII with the rule of zero, correct smart-pointer selection, and move semantics. Use when writing modern C++ or reviewing code with manual new/delete or leaky error paths.4---56# C++ RAII78Every resource (memory, file, lock, socket, GPU handle) lives in an9object whose constructor acquires and destructor releases. Once that10is true everywhere, early returns and exceptions cannot leak, and11cleanup order becomes deterministic scope order.1213## Method14151. **Aim for the rule of zero.** Compose classes from members that16 already manage themselves (`std::string`, `std::vector`,17 `unique_ptr`, `lock_guard`); then the compiler-generated special18 members are correct and you write none of them. Only resource-19 *owning* wrapper classes implement the rule of five, and each20 such class does exactly one job: own that resource.212. **Pick the pointer by ownership, not habit.**22 `std::unique_ptr` is the default for owned heap objects (zero23 overhead, moves express transfer); `std::shared_ptr` only when24 lifetime is genuinely shared by design (and `weak_ptr` breaks the25 cycles); raw pointers and references mean "borrowed, non-owning,26 never freed by me": document lifetime expectations at the API27 (see rust-ownership for the same taxonomy enforced by a28 compiler).293. **Allocate with make functions, never naked new.**30 `std::make_unique`/`make_shared` tie allocation to the owner in31 one expression; a `new` outside a smart-pointer constructor is a32 review flag. `delete` appearing anywhere outside a custom33 deleter means an ownership hole.344. **Wrap every non-memory resource the same way.** Files, mutexes35 (`lock_guard`/`unique_lock`, see deadlock-analysis), transactions36 (commit/rollback in destructor via scope guards), C handles from37 libraries (`unique_ptr` with custom deleter). If cleanup appears38 in function bodies instead of destructors, error paths will39 eventually skip it.405. **Use move semantics as ownership transfer syntax.** Return by41 value (RVO/moves make it cheap), take sinks by value and move42 into place, mark moved-from objects as valid-but-empty. Write43 `noexcept` on moves (containers fall back to copies otherwise);44 never throw from destructors: report or terminate, because a45 throwing destructor during unwind ends the process.466. **Let scope express lifetime in reviews.** The audit question for47 any function: every acquired thing owned by a named object whose48 scope matches the intended lifetime? Ad hoc cleanup blocks,49 `goto`-style flags, and paired init/teardown calls are the C50 idiom this skill replaces (see c-memory-safety when C interop51 forces it).5253## Boundaries5455- `shared_ptr` is not a garbage collector: cycles leak, atomic56 refcounts cost, and "shared because deciding an owner was hard"57 is deferred design debt.58- RAII manages resources, not object relationships; parent-child59 back-pointers and observer lists still need lifetime thought60 (weak_ptr, handles, or explicit unregistration).61- Interop with C APIs and exception-unsafe legacy code confines62 RAII benefits to the wrapped boundary; wrap at the edge63 immediately rather than letting raw handles roam (see64 ffi-boundaries).