Raii
By the end of this lesson you'll be able to write a class whose constructor grabs a resource and whose destructor frees it — so files, locks, and memory clean themselves up the instant they go out of scope, even when an exception is flying through your code.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
RAII is a hotel key card . When you check in (the object is constructed ) you are handed access to your room. When you check out (the object is destroyed ) access is revoked — you literally cannot leave with the room still "open" because checkout is automatic at the end of your stay. You never have to remember to hand the key back: leaving the building (the scope) does it for you. A leaked resource in C++ is like walking off with the key still active; RAII removes that possibility by making release part of leaving.
1. The Core Idea: Constructor Acquires, Destructor Releases
RAII (Resource Acquisition Is Initialization) ties the lifetime of a resource — a file, a lock, heap memory, a network socket — to the lifetime of an object . You acquire the resource in the constructor and release it in the destructor. Because C++ guarantees a destructor runs the moment an object leaves scope, cleanup becomes deterministic : it happens at a known point, every time, with no garbage collector and no manual free() . Read this tiny worked example, run it, and watch the destructor fire on its own.
2. Why RAII Makes Code Exception-Safe
Here is the part that matters most. When an exception is thrown, C++ unwinds the stack — it destroys every local object between the throw and the matching catch , running each destructor on the way out. So if your resource lives in an RAII object, it is released even when something goes wrong . The file below is closed correctly despite an exception blowing up halfway through. Notice there is no close() in a catch block anywhere — the destructor does it.
Your turn. Below is a Door that must be locked then unlocked. Wrap it in an RAII guard so the unlock can never be forgotten. Fill in the three blanks marked ___ using the hints, then run it.
3. The Standard Library Is Already Full of RAII
You rarely need to write resource cleanup yourself, because the standard library ships RAII wrappers for the common resources. std::unique_ptr owns heap memory and calls delete for you. std::lock_guard locks a std::mutex on construction and unlocks it on destruction. std::fstream opens a file and closes it in its destructor. Notice in the example that the three resources are released in reverse order of acquisition — that is just normal destructor order, and it is exactly what you want for nested resources.
Now you pick the right tool. Replace each ___ with the correct standard RAII type so the int frees itself and the mutex unlocks itself at the end of the block.
The rule of zero is the modern default: if every member of your class is already an RAII type ( unique_ptr , string , vector , fstream …), write no destructor and no copy/move operations. The compiler-generated ones already do the correct thing, member by member. Most classes you write should have an empty special-member section.
You only step up to the rule of three when your class owns a raw resource directly — a FILE* , a socket file descriptor, a new 'd pointer. The rule says: if you write a destructor, you almost certainly also need a copy constructor and a copy-assignment operator, because the default ones would copy the raw handle and then release it twice. The rule of five adds a move constructor and move assignment so the resource can transfer ownership efficiently.
The takeaway: prefer the rule of zero by building from RAII members. Reach for three/five only at the thin boundary where you wrap a raw C resource.
No blanks this time — just a brief and an outline. Write a full RAII wrapper from scratch: acquire in the constructor, release in the destructor, and ban copying so the connection can't be closed twice. Run it and check your output against the expected lines in the comments.
Practice quiz
What does RAII stand for?
- Random Access Index Initialization
- Recursive Allocation In Initialization
- Resource Acquisition Is Initialization
- Reference And Index Inheritance
Answer: Resource Acquisition Is Initialization. RAII means Resource Acquisition Is Initialization — tie a resource's lifetime to an object's lifetime.
In RAII, where is a resource acquired and where is it released?
- Acquired in the constructor, released in the destructor
- Acquired in main, released in a catch block
- Acquired and released by the garbage collector
- Acquired in the destructor, released in the constructor
Answer: Acquired in the constructor, released in the destructor. The constructor acquires the resource and the destructor releases it automatically at scope exit.
Why is RAII better than manually calling close() or free() at the end of a function?
- It is faster at runtime
- It avoids needing a constructor
- It only works in release builds
- A destructor runs on every exit path (including exceptions), so the cleanup can't be skipped
Answer: A destructor runs on every exit path (including exceptions), so the cleanup can't be skipped. Manual cleanup is skipped by early returns or exceptions; a destructor is guaranteed to run on every exit path.
What makes RAII exception-safe specifically?
- Exceptions are disabled inside RAII classes
- During stack unwinding, C++ runs the destructor of every local object between throw and catch
- RAII catches all exceptions automatically
- The compiler inserts free() calls
Answer: During stack unwinding, C++ runs the destructor of every local object between throw and catch. When an exception unwinds the stack, each local's destructor runs, so RAII resources are released even on errors.
Which of these is a ready-made standard-library RAII type for a mutex?
- std::lock_guard
- std::vector
- std::string
- std::optional
Answer: std::lock_guard. std::lock_guard locks a mutex on construction and unlocks it in its destructor.
When three RAII objects are created in one scope, in what order are they destroyed at the end of the scope?
- The same order they were created
- Random order
- Reverse order of acquisition (last created is destroyed first)
- Alphabetical order of their names
Answer: Reverse order of acquisition (last created is destroyed first). Local objects are destroyed in reverse order of construction — exactly right for nested resources.
What is the 'rule of zero'?
- Never write any classes
- If members are already RAII types, write no destructor, copy, or move operations — the defaults are correct
- Always delete the destructor
- Initialize every member to zero
Answer: If members are already RAII types, write no destructor, copy, or move operations — the defaults are correct. With RAII members, the compiler-generated special members do the right thing, so you write none of them.
When do you need the rule of three (or five)?
- For every class you write
- Only for templates
- Never in modern C++
- When a class directly manages a raw resource (e.g. a FILE* or new'd pointer)
Answer: When a class directly manages a raw resource (e.g. a FILE* or new'd pointer). Hand-managing a raw resource means writing a destructor implies also writing copy (and usually move) operations.
Why does an RAII wrapper for a unique resource often = delete its copy constructor?
- To make the class smaller
- So two copies can't both release the same resource (a double-release / double-free)
- To allow copying without overhead
- Because copy constructors are deprecated
Answer: So two copies can't both release the same resource (a double-release / double-free). Copying a unique handle would let both copies release the same resource at scope end — banning copy prevents that.
What happens if an exception escapes a destructor during stack unwinding?
- It is caught by the nearest try block
- Nothing — it is ignored
- The program calls std::terminate and dies
- The destructor is retried
Answer: The program calls std::terminate and dies. Throwing from a destructor while already unwinding calls std::terminate, so destructors must release quietly.