RAII
Why RAII matters so much
Every resource management bug covered earlier in this section — memory leaks, forgetting to close a file, forgetting to unlock a mutex — has the same shape: some cleanup step needs to run no matter how a block of code is exited, including early returns and thrown exceptions. Manually remembering to clean up on every single exit path is fragile. RAII sidesteps the whole problem: as long as the resource is owned by an object with a proper destructor, cleanup happens automatically, unconditionally, the instant that object goes out of scope.
RAII beyond memory
Resource | RAII wrapper | What the destructor does |
|---|---|---|
Heap memory | std::unique_ptr / std::shared_ptr | Calls delete on the owned object |
File handle | std::fstream / std::ifstream / std::ofstream | Closes the file |
Mutex lock | std::lock_guard / std::unique_lock | Unlocks the mutex |
Network connection | A custom RAII wrapper class | Closes the socket/connection |
A worked example: a simple RAII file wrapper
Here is a minimal RAII class wrapping a C-style file handle. The constructor opens the file; the destructor closes it, no matter how the enclosing scope is exited.
raii_file_wrapper.cpp
#include <cstdio>
#include <stdexcept>
#include <iostream>
class FileHandle {
public:
explicit FileHandle(const char* path) {
file_ = std::fopen(path, "w");
if (!file_) {
throw std::runtime_error("Failed to open file");
}
}
// Destructor: guaranteed to run when a FileHandle goes out of scope,
// whether normally, via early return, or via an exception unwinding.
~FileHandle() {
if (file_) {
std::fclose(file_);
std::cout << "File closed automatically" << std::endl;
}
}
void write(const char* text) {
std::fputs(text, file_);
}
// Prevent copying -- this simple wrapper only supports single ownership,
// just like std::unique_ptr.
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
private:
std::FILE* file_;
};
void writeLog() {
FileHandle log("log.txt"); // resource acquired in the constructor
log.write("Starting up...\n");
// Even if code here throws an exception or returns early,
// log's destructor still runs and the file still gets closed.
log.write("Done.\n");
} // log goes out of scope here -> file closed automaticallyRAII in the standard library
You have already been using RAII throughout this tutorial without necessarily naming it. Three of the most common examples:
std::unique_ptr — acquires ownership of a heap object in its constructor, deletes it in its destructor.
std::lock_guard — locks a mutex in its constructor, unlocks it in its destructor, guaranteeing the lock is released even if an exception is thrown while it's held.
std::fstream (and std::ifstream / std::ofstream) — opens a file in its constructor, closes it in its destructor.
raii_stdlib_examples.cpp
#include <fstream>
#include <memory>
#include <mutex>
std::mutex mtx;
void example() {
std::ofstream out("data.txt"); // RAII: file opens here
out << "hello";
// no need to call out.close() -- it happens automatically
{
std::lock_guard<std::mutex> lock(mtx); // RAII: mutex locks here
// ... critical section ...
} // mutex unlocks automatically here, even if an exception was thrown
auto ptr = std::make_unique<int>(42); // RAII: memory allocated here
} // ptr's memory, and out's file, are both cleaned up automatically here