RAII as a Pattern
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 102 of 104.
RAII (Resource Acquisition Is Initialization) is more than just a C++ idiom - it's a powerful design pattern that ties resource management to object lifetime. You've already seen RAII with smart pointers, but the pattern applies to any resource: file handles, network connections, mutexes, or database transactions.
The core idea is simple: acquire resources in the constructor, release them in the destructor. Since C++ guarantees destructors run when objects go out of scope, cleanup happens automatically - even when exceptions occur:
#include <iostream>
#include <fstream>
class FileGuard {
std::ofstream file;
public:
FileGuard(const std::string& filename) : file(filename) {
if (!file.is_open()) {
std::cout << "Failed to open file\n";
}
}
void write(const std::string& text) {
if (file.is_open()) file << text;
}
~FileGuard() {
if (file.is_open()) {
file.close();
std::cout << "File closed automatically\n";
}
}
};
int main() {
{
FileGuard guard("output.txt");
guard.write("Hello RAII");
} // Destructor called here - file closed
std::cout << "After scope\n";
}RAII shines when managing locks in multithreaded code. The standard library's std::lock_guard follows this pattern - it acquires a mutex on construction and releases it on destruction, preventing deadlocks from forgotten unlocks.
When implementing RAII classes, remember to either delete or properly implement copy/move operations (Rule of Five) to prevent resource duplication or double-release issues. RAII transforms error-prone manual resource management into safe, automatic cleanup.
Challenge
EasyLet's build a Connection Pool Manager using RAII to safely manage database connections. In real applications, database connections are expensive resources that must be properly acquired and released. You'll create an RAII wrapper that guarantees connections are always returned to the pool, even if exceptions occur or code paths get complicated.
You'll organize your code across three files:
ConnectionPool.h: Create a simple connection pool that manages a limited number of connections.Your
ConnectionPoolclass should track how many connections are available (start with a capacity passed to the constructor). Implement:acquire()— if a connection is available, decrease the count and printConnection acquired (X available)where X is the remaining count; returntrueif successful,falseif no connections availablerelease()— increase the available count and printConnection released (X available)available()— returns the current number of available connections
ConnectionGuard.h: Build the RAII wrapper that safely manages a single connection.Your
ConnectionGuardclass embodies the RAII pattern. It should:- Take a reference to a
ConnectionPoolin its constructor and attempt to acquire a connection - Store whether the acquisition was successful
- Provide an
isConnected()method to check if the guard holds a valid connection - Automatically release the connection back to the pool in the destructor (only if one was acquired)
- Delete copy constructor and copy assignment to prevent resource duplication (Rule of Five consideration)
When the destructor runs, if a connection was held, print
Guard releasing connectionbefore calling release on the pool.- Take a reference to a
main.cpp: Demonstrate RAII's automatic cleanup through scopes.Read two inputs:
- Pool capacity (integer)
- Number of connections to request (integer)
Create a
ConnectionPoolwith the given capacity. Then, inside a nested scope (using curly braces), create the requested number ofConnectionGuardobjects stored in a vector. For each guard, print whether it connected successfully:- If connected:
Guard N: Connected - If not connected:
Guard N: Failed to connect
(where N starts at 1)
After the scope ends (guards are destroyed), print
After scope: X connections availableshowing the pool's final state.
For example, with inputs 2 and 3:
Connection acquired (1 available)
Guard 1: Connected
Connection acquired (0 available)
Guard 2: Connected
Guard 3: Failed to connect
Guard releasing connection
Connection released (1 available)
Guard releasing connection
Connection released (2 available)
After scope: 2 connections availableWith inputs 3 and 2:
Connection acquired (2 available)
Guard 1: Connected
Connection acquired (1 available)
Guard 2: Connected
Guard releasing connection
Connection released (2 available)
Guard releasing connection
Connection released (3 available)
After scope: 3 connections availableNotice how the connections are automatically released when the guards go out of scope—you never explicitly call release in your main code. The destructors run in reverse order of construction (last guard destroyed first), and every acquired connection is guaranteed to be returned. This is the power of RAII: resource cleanup happens automatically and reliably, no matter how the scope exits.
Cheat sheet
RAII (Resource Acquisition Is Initialization) is a design pattern that ties resource management to object lifetime. Resources are acquired in the constructor and released in the destructor.
Since C++ guarantees destructors run when objects go out of scope, cleanup happens automatically—even when exceptions occur:
class FileGuard {
std::ofstream file;
public:
FileGuard(const std::string& filename) : file(filename) {
if (!file.is_open()) {
std::cout << "Failed to open file\n";
}
}
void write(const std::string& text) {
if (file.is_open()) file << text;
}
~FileGuard() {
if (file.is_open()) {
file.close();
std::cout << "File closed automatically\n";
}
}
};
int main() {
{
FileGuard guard("output.txt");
guard.write("Hello RAII");
} // Destructor called here - file closed automatically
std::cout << "After scope\n";
}RAII is particularly useful for managing locks in multithreaded code. The standard library's std::lock_guard acquires a mutex on construction and releases it on destruction, preventing deadlocks from forgotten unlocks.
When implementing RAII classes, delete or properly implement copy/move operations (Rule of Five) to prevent resource duplication or double-release issues.
Try it yourself
#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"
using namespace std;
int main() {
// Read inputs
int capacity;
int numConnections;
cin >> capacity;
cin >> numConnections;
// TODO: Create a ConnectionPool with the given capacity
// TODO: Create a nested scope using curly braces
{
// TODO: Create a vector to store ConnectionGuard objects
// Hint: You'll need to use pointers or smart pointers since ConnectionGuard
// has deleted copy constructor
// TODO: Loop to create numConnections guards
// For each guard, print either:
// "Guard N: Connected" or "Guard N: Failed to connect"
// where N starts at 1
}
// Guards are destroyed here when scope ends
// TODO: Print "After scope: X connections available"
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRAII as a Pattern3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container