RAII: Resources That Clean Up After Themselves
RAII — "Resource Acquisition Is Initialisation" — is the most important idea in C++. A resource (memory, a file, a lock, a network connection) is owned by an object: the constructor acquires it and the destructor releases it. Because destructors always run when objects go out of scope, cleanup happens automatically — even when errors occur.
The idea
#include <iostream> #include <stdexcept> #include <string> class Connection { public: explicit Connection(std::string host) : host_(std::move(host)) { std::cout << "open " << host_ << '\n'; } ~Connection() { std::cout << "close " << host_ << '\n'; } void send(const std::string& message) { if (message.empty()) throw std::runtime_error("empty message"); std::cout << "send \"" << message << "\" to " << host_ << '\n'; } private: std::string host_; }; void talk() { Connection a{"db.example.com"}; Connection b{"cache.example.com"}; a.send("hello"); b.send(""); // throws a.send("never sent"); } int main() { try { talk(); } catch (const std::exception& e) { std::cout << "error: " << e.what() << '\n'; } }
open db.example.com open cache.example.com send "hello" to db.example.com close cache.example.com close db.example.com error: empty message
When the exception leaves talk, both connections are closed — in reverse order of opening — before the catch runs. No finally, no clean-up code to forget.
RAII in the standard library
You've been using RAII all along:
| Resource | RAII type |
|---|---|
| Heap memory for many elements | std::vector, std::string, std::map… |
| A single heap object | std::unique_ptr, std::shared_ptr |
| An open file | std::ifstream, std::ofstream (lesson 44) |
| A mutex lock | std::lock_guard, std::scoped_lock (lesson 47) |
| A thread | std::jthread (C++20; joins in its destructor) |
#include <fstream> #include <iostream> #include <string> int main() { { std::ofstream out{"greeting.txt"}; // file opened out << "Hello from RAII\n"; } // file flushed and closed here std::ifstream in{"greeting.txt"}; std::string line; std::getline(in, line); std::cout << line << '\n'; }
Hello from RAII
Wrapping a C resource
C libraries hand out resources that must be released with a specific function. A small RAII class — or a unique_ptr with a custom deleter — makes them safe:
#include <cstdio> #include <iostream> #include <memory> struct FileCloser { void operator()(std::FILE* f) const { if (f) { std::fclose(f); std::cout << "fclose called\n"; } } }; using FilePtr = std::unique_ptr<std::FILE, FileCloser>; int main() { FilePtr f{std::fopen("log.txt", "w")}; if (!f) return 1; std::fputs("written through a C API\n", f.get()); std::cout << "leaving main\n"; }
leaving main fclose called
Scope guards
For one-off clean-up actions, a tiny generic "run this on scope exit" helper does the job:
#include <iostream> #include <utility> template <typename F> class ScopeExit { public: explicit ScopeExit(F f) : f_(std::move(f)) {} ~ScopeExit() { f_(); } ScopeExit(const ScopeExit&) = delete; ScopeExit& operator=(const ScopeExit&) = delete; private: F f_; }; int main() { std::cout << "start task\n"; ScopeExit done{[] { std::cout << "task finished (always printed)\n"; }}; std::cout << "working...\n"; }
start task working... task finished (always printed)
The rule of zero
If every member of your class manages itself (strings, vectors, smart pointers), the class needs no destructor, copy or move operations of its own — the compiler-generated ones do the right thing. This "rule of zero" is the goal for most classes. Only classes that directly manage a raw resource need to write those functions (lesson 35).
Try it yourself
Write a Timer class that prints a label when it's created and again when it's destroyed, and use it to mark the start and end of a block.
[start] main [start] iteration 1 doing work 1 [end] iteration 1 [start] iteration 2 doing work 2 [end] iteration 2 [end] main