C++ by Example
C++ by Example

RAII

2 min read

RAII

RAII — Resource Acquisition Is Initialization — is the most important idiom in C++. The idea: tie a resource's lifetime to an object's lifetime. The constructor acquires the resource; the destructor releases it. Because destructors run automatically at end of scope, the resource is always released, even if an exception is thrown.

The pattern

Without RAII:

void process_file(const std::string& path) {
    FILE* f = fopen(path.c_str(), "r");
    // ... work ...
    // If an exception is thrown here, fclose never runs
    fclose(f);
}

With RAII:

#include <fstream>
#include <string>

void process_file(const std::string& path) {
    std::ifstream f(path);   // constructor opens the file
    // ... work ...
}                            // destructor closes it — always, even on exception

Writing a RAII wrapper

#include <iostream>
#include <stdexcept>

class MutexGuard {
    pthread_mutex_t* mutex;
public:
    explicit MutexGuard(pthread_mutex_t* m) : mutex(m) {
        pthread_mutex_lock(mutex);
    }
    ~MutexGuard() {
        pthread_mutex_unlock(mutex);
    }
    MutexGuard(const MutexGuard&) = delete;            // non-copyable
    MutexGuard& operator=(const MutexGuard&) = delete; // non-assignable
};

This pattern is exactly what std::lock_guard does in the standard library.

The standard library uses RAII everywhere

Resource RAII wrapper
Heap memory std::unique_ptr, std::shared_ptr
File handle std::fstream, std::ifstream, std::ofstream
Mutex std::lock_guard, std::unique_lock
Thread std::thread (join on destruction)
Temporary buffer std::vector, std::string

A concrete example

#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>

std::string read_file(const std::string& path) {
    std::ifstream f(path);
    if (!f.is_open()) throw std::runtime_error("cannot open " + path);

    return std::string(
        std::istreambuf_iterator<char>(f),
        std::istreambuf_iterator<char>()
    );
}   // f closes here — no explicit fclose needed

int main() {
    try {
        std::string content = read_file("example.txt");
        std::cout << content.size() << " bytes\n";
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
    }
}

Why RAII matters

Consider a function that acquires several resources:

void multi_resource() {
    std::lock_guard<std::mutex> lock(my_mutex);
    std::ifstream   file("data.txt");
    auto            buf = std::make_unique<char[]>(4096);
    // ...
}
// all three released automatically in reverse order of acquisition

Without RAII, you would need try/catch blocks or careful cleanup paths at every exit. With RAII, the destructor handles everything. The code becomes shorter, clearer, and exception-safe.

Destructors must not throw

Destructors run during stack unwinding when an exception propagates. If a destructor throws, std::terminate is called and the program aborts. Mark destructors noexcept (which they are by default) and handle errors inside them without throwing.

C++ by Example
C++ by Example

Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.

View book →