RAII

Resource Acquisition Is Initialization: bind a resource (memory, file, lock, socket) to the lifetime of a stack object so its destructor releases the resource automatically.

Why it matters

RAII is the C++ answer to “who cleans up?” and it makes correctness automatic even across early returns and exceptions — the destructor runs no matter how the scope exits (exception-safety-guarantees). It is why idiomatic modern C++ rarely needs try/finally: the standard library’s smart pointers, std::lock_guard, std::fstream, and std::vector are all RAII wrappers that turn manual new-delete into automatic cleanup.

How it works

A guard object acquires in its constructor and releases in its destructor. Because the compiler guarantees object-lifetime semantics — destructors fire at scope exit in reverse construction order — release is tied to a code region, not to disciplined manual calls.

  • Acquire in ctor, release in dtor. Half-constructed objects don’t run the dtor, which is why ctors must not leak.
  • One owner per resource. Copying a naive guard double-releases; obey the-rule-of-0-3-5 (delete copies, or define move).
  • Stack unwinding during an exception destroys every fully-built local on the way out, releasing each resource exactly once.
ResourceRAII wrapper
heap objectstd::unique_ptr / std::shared_ptr
mutexstd::lock_guard / std::scoped_lock
file handlestd::fstream
arbitrary cleanupcustom guard / std::unique_ptr with deleter

Example

void write(const std::string& path, std::span<const char> data) {
  std::lock_guard lk(g_mtx);        // acquire lock
  std::ofstream out(path);          // acquire file
  out.write(data.data(), data.size());
}  // out closed, then lk unlocked — even if write() throws

No close(), no unlock(), no leak on the throw path: both destructors run during unwinding, in reverse order (out then lk).

Pitfalls

  • Leaking the guard: new Guard(...) puts the guard on the heap, so its dtor never runs at scope exit. Keep guards as locals.
  • Naming a temporary out of existence: std::lock_guard(m); creates an unnamed temporary that dies immediately, locking nothing. Always name it.
  • Throwing destructors during unwinding call std::terminate; dtors should be noexcept.
  • Copyable guards without proper copy/move semantics double-release — see the-rule-of-0-3-5.

See also