RAII — resource acquisition is initialization

RoadmapsC++

Scenario

You lock a mutex, write to a file, and unlock the mutex. During the file write, an unexpected `std::runtime_error` is thrown. The function aborts, the mutex is never unlocked, and your entire application deadlocks.

How can you guarantee the mutex unlocks even if the function is violently aborted by an exception?

Mental model

RAII is like having an automated clean-up crew physically handcuffed to your variable. The moment the variable dies (goes out of scope), the clean-up crew is violently yanked down with it, forcing them to do their job on the way out.

RAII (Resource Acquisition Is Initialization) is the defining idiom of C++. It binds the life cycle of a resource (heap memory, open files, network sockets, mutex locks) to the lifespan of a stack-allocated local variable. When the variable goes out of scope, its destructor is guaranteed to run, automatically cleaning up the resource.

Explanation

In languages like Java or C#, you use `finally` blocks or Garbage Collection to clean up resources. C++ does not have a garbage collector, and it doesn't need a `finally` block because of RAII.

**How it works:**

1. **Acquisition:** You wrap the raw resource inside a class. The constructor acquires the resource (e.g., opens a file, locks a mutex).

2. **Initialization:** You instantiate this class as a local, stack-allocated variable.

3. **Destruction:** The C++ language guarantees that when a stack variable goes out of scope—whether by hitting a `return` statement, reaching the end of the block, or being violently interrupted by an `throw`—its destructor will run. The destructor releases the resource (e.g., closes the file, unlocks the mutex).

This is why `std::unique_ptr`, `std::vector`, and `std::lock_guard` exist. They are all RAII wrappers. `std::vector` owns a raw heap pointer, and its destructor calls `delete[]` so you never have to.

Code examples

The bug: Manual resource management

#include <mutex>

std::mutex mtx;

void riskyFunction() {
    mtx.lock();
    
    // If this throws, the function aborts immediately.
    // mtx.unlock() is NEVER reached. The program deadlocks.
    processData(); 
    
    mtx.unlock(); 
}

Managing resources manually requires perfect discipline. Any early return or thrown exception breaks the cleanup flow.

The fix: RAII (std::lock_guard)

#include <mutex>

std::mutex mtx;

void safeFunction() {
    // Acquisition is Initialization:
    // The constructor of lock_guard locks the mutex.
    std::lock_guard<std::mutex> guard(mtx);
    
    // If this throws, 'guard' goes out of scope as the stack unwinds.
    // Its destructor fires, automatically unlocking the mutex.
    processData();
    
    // Destructor runs here naturally as well.
}

Because `guard` is a local stack variable, C++ guarantees its destructor runs no matter how the function exits. The lock is tied to the scope itself.

Key points

Common mistakes

Recall questions

Questions & answers

A developer writes a custom RAII `FileHandler` class. They instantiate it using `FileHandler* fh = new FileHandler("data.txt");`. When the function returns, the file remains open. Why?

Because `fh` is a raw pointer on the stack, not an RAII object on the stack. When the function returns, the pointer is destroyed, but the actual `FileHandler` object on the heap is not destroyed, so its destructor never runs. RAII objects must be stack-allocated.

Approach: Understand that RAII relies entirely on deterministic stack-variable destruction.

If you throw an exception inside a function, how does C++ ensure a `std::lock_guard` releases its lock?

C++ performs 'stack unwinding' when an exception is thrown, which calls the destructors of all local stack variables (including the `lock_guard`) before propagating the exception up the call stack.

Approach: Understand how stack unwinding guarantees RAII destruction.

Continue learning

Previous: Classes, constructors, destructors & this

Previous: Memory leaks, dangling pointers & double free

Next: unique_ptr, shared_ptr & weak_ptr

Return to C++ Roadmap