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.
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 →