Skip to content

Intermediate · Classes, constructors and RAII · topic 15 of 42 · 25 min

RAII: let objects clean up for you

Explanation

RAII (Resource Acquisition Is Initialisation) means: get a resource in a constructor and release it in the destructor. Because destructors always run when an object goes out of scope, even when an error is thrown, the resource can never be forgotten. std::vector, std::string, std::fstream, std::lock_guard and smart pointers all use RAII.

What is a resource?

A resource is anything your program must give back after using it: heap memory, an open file, a network connection, a database connection, or a lock that stops two threads from clashing. Forgetting to give back a resource is a leak. Leaks pile up in long-running programs like servers until the machine runs out of memory or file handles.

The problem with manual cleanup

cpp
void saveReport() {
    FILE* f = std::fopen("report.txt", "w");
    if (!f) return;
    if (!writeHeader(f)) return;      // BUG: file never closed on this path
    writeBody(f);                     // if this throws, the file is never closed
    std::fclose(f);
}
Every early return and every exception is a chance to forget the cleanup.

The RAII idea

RAII stands for "Resource Acquisition Is Initialisation". The name is clumsy but the idea is simple: wrap each resource in an object. The constructor acquires the resource; the destructor releases it. C++ guarantees that the destructor of a local object runs when the scope ends, whether the function returns normally, returns early, or an exception passes through. So cleanup happens automatically and exactly once.

Think of it like this: RAII is like a library book with an automatic return: the moment you walk out of the reading room (the scope), the book goes back to the shelf by itself, whether you left calmly, ran out because of a fire alarm (an exception), or left early. You cannot forget to return it.
cpp
#include <fstream>
#include <string>

void saveReport(const std::string& text) {
    std::ofstream out("report.txt");     // constructor opens the file
    if (!out) return;
    out << "Monthly report\n";
    out << text << '\n';
}                                         // destructor closes the file, on every path
std::ofstream is an RAII class: no close() call is needed.

Writing your own RAII class

cpp
#include <cstdio>
#include <stdexcept>

class File {
public:
    File(const char* path, const char* mode) : f_(std::fopen(path, mode)) {
        if (!f_) throw std::runtime_error("cannot open file");
    }
    ~File() { std::fclose(f_); }
    File(const File&) = delete;              // no copies: two owners would close twice
    File& operator=(const File&) = delete;
    std::FILE* get() const { return f_; }
private:
    std::FILE* f_;
};

void writeLog() {
    File log("app.log", "a");
    std::fputs("started\n", log.get());
}   // closed here automatically
The class owns the FILE*. Copying is deleted because two copies would both try to close the same file.

RAII is everywhere in the standard library

ClassResource it manages
std::string, std::vector, std::mapheap memory for their elements
std::ifstream, std::ofstreaman open file
std::unique_ptr, std::shared_ptra heap object
std::lock_guard, std::scoped_locka mutex lock
std::jthread (C++20)a thread (joined automatically)

This is why modern C++ code rarely contains delete, fclose or unlock. If you see a manual cleanup call, ask: "which RAII class should own this?"

Scope controls lifetime

cpp
void process() {
    {
        std::lock_guard<std::mutex> lock(m);   // lock taken
        shared.push_back(42);
    }                                           // lock released here
    slowWorkThatNeedsNoLock();
}
An extra pair of braces makes a smaller scope, so the resource is held only as long as needed.
Careful: RAII only works for objects with automatic lifetime (locals, members, container elements). If you create the wrapper itself with new and forget delete, its destructor never runs. Do not new your RAII objects; put them on the stack or inside a smart pointer.
Tip: C++ Core Guidelines rule R.1: "Manage resources automatically using resource handles and RAII". When you design a class that owns something, decide its copy behaviour at the same time (next topic).

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

RAII: let objects clean up for you · 1/6

RAII

RAII is the most important idea in C plus plus. It stops leaks of memory, files and locks.

Prefer a video by a teacher? Find free videos on “c++ RAII explained resource acquisition is initialization” on YouTube ↗

In real life: Database connections in a booking server

A ticket-booking server borrows a database connection from a pool for every request. If a request fails halfway with an exception and the connection is never returned, the pool slowly empties and the site stops responding during a Tatkal rush. Wrapping the borrowed connection in an RAII object returns it to the pool in the destructor, on every path.

cpp
class PooledConnection {
public:
    explicit PooledConnection(Pool& pool) : pool_(pool), conn_(pool.acquire()) {}
    ~PooledConnection() { pool_.release(conn_); }
    PooledConnection(const PooledConnection&) = delete;
    PooledConnection& operator=(const PooledConnection&) = delete;
    Connection& operator*() { return *conn_; }
private:
    Pool& pool_;
    Connection* conn_;
};

void bookSeat(Pool& pool, int seat) {
    PooledConnection db(pool);
    (*db).execute("UPDATE seats SET booked = 1 WHERE id = ?", seat);  // may throw
}   // connection returned to the pool, even if execute threw
Pool and Connection stand for a real database library; the RAII pattern is the point.

Practice (0/4 done)

Task 1 · Question

What does RAII guarantee?

Task 2 · Question

Which of these is NOT an RAII type?

Task 3 · Question

Why does the File RAII class delete its copy constructor?

Task 4 · Practice

Write an RAII class ScopedMessage that prints "BEGIN <name>" when created and "END <name>" when destroyed. Use it in a function that has an early return and in one that throws an exception (catch it in main). Check that END is always printed.

Quick quiz

Question 1

1.What does the R in RAII stand for?

Question 2

2.An exception is thrown inside a function. What happens to the local objects already created there?

Question 3

3.Which is the RAII way to hold a mutex lock?

Question 4

4.Why should you avoid creating an RAII object with new?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does the R in RAII stand for?

Answer: Resource

Resource Acquisition Is Initialisation.

An exception is thrown inside a function. What happens to the local objects already created there?

Answer: Their destructors run as the exception leaves the scope (stack unwinding)

Stack unwinding destroys local objects in reverse order, which is what makes RAII reliable.

Which is the RAII way to hold a mutex lock?

Answer: std::lock_guard<std::mutex> g(m);

lock_guard locks in its constructor and unlocks in its destructor.

Why should you avoid creating an RAII object with new?

Answer: Its destructor only runs when you delete it, so the automatic cleanup is lost

RAII relies on automatic lifetime. A heap object lives until someone deletes it.

How do you pass ownership of a unique_ptr into a function sink(std::unique_ptr<T> p)?Related

Answer: sink(std::move(p))

Ownership must be moved explicitly.

From Smart pointers 1: std::unique_ptr

When does a local object's destructor run?Related

Answer: When the object goes out of scope

Local objects are destroyed automatically at the end of their scope.

From Constructors and destructors

Which pair makes a deep copy automatically when you copy the object?Related

Answer: A std::vector member

std::vector copies all its elements into new memory.

From Copying objects: the rule of three, five and zero

Which base class should your own exception types usually derive from?Related

Answer: std::exception (often via std::runtime_error)

Then generic handlers for std::exception catch them too, and what() works.

From Exceptions: throw, try, catch and noexcept

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Classes, constructors and RAII” before its exam: Copying objects: the rule of three, five and zero.
  2. Module 4 exam: Classes, constructors and RAII · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Operator overloading, inheritance and polymorphism. 42 topics left in the course before the final exam.

← Previous: Constructors and destructors