DEV Community

Cover image for RAII Explained: Why C++ Doesn't Need a Garbage Collector
Sk
Sk

Posted on

RAII Explained: Why C++ Doesn't Need a Garbage Collector

RAII is a core C++ philosophy you can't skip. It ties resources; files, memory, locks, directly to object lifetimes and scope.

Think of it as a localized garbage collector(gc) under your control.

It's a blueprint that tells the compiler to insert cleanup code at crucial spots; like defer in Zig or Go, that runs at the end of a scope.

By "blueprint" I mean, a way of structuring code(the rule of 5 below) so the compiler automatically generates cleanup tied to scope.


The Rule of Five

Here's the classic RAII skeleton:

class ResourceHolder {
public:
    ResourceHolder();                      // Constructor
    ~ResourceHolder();                     // Destructor

    ResourceHolder(const ResourceHolder&);             // Copy constructor
    ResourceHolder& operator=(const ResourceHolder&);  // Copy assignment

    ResourceHolder(ResourceHolder&&) noexcept;             // Move constructor
    ResourceHolder& operator=(ResourceHolder&&) noexcept;  // Move assignment
};
Enter fullscreen mode Exit fullscreen mode

Want to ban copying? Delete it:

ResourceHolder(const ResourceHolder&) = delete;
ResourceHolder& operator=(const ResourceHolder&) = delete;
Enter fullscreen mode Exit fullscreen mode

Once you grasp self-regulating objects, you learn to lean on smart pointers for ownership:

std::unique_ptr<ResourceHolder> a;  
a = std::make_unique<ResourceHolder>(); // one owner at a time
std::shared_ptr<ResourceHolder> b;      // multiple owners allowed
Enter fullscreen mode Exit fullscreen mode

Smart pointers track ownership automatically, once no one owns an object, it cleans itself up(like a localized gc).


The RAII Template With Examples

For RAII, the bare minimum is:

  • a constructor (acquire the resource),
  • a destructor (release the resource).

Copy/move can be defaulted or customized depending on your needs.

Example: managing a file safely.

class File {
    std::fstream file;

public:
    File(const std::string& filename, std::ios::openmode mode)
        : file(filename, mode) 
    {
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open file: " + filename);
        }
    }

    ~File() {
        if (file.is_open()) {
            file.close();
        }
    }

    // Prevent copying
    File(const File&) = delete;
    File& operator=(const File&) = delete;

    // Allow moving
    File(File&& other) noexcept : file(std::move(other.file)) {}
    File& operator=(File&& other) noexcept {
        if (this != &other) {
            file = std::move(other.file);
        }
        return *this;
    }
};
Enter fullscreen mode Exit fullscreen mode

Standard Library RAII

RAII is also baked into the standard template library(STL).

void simple_example() {
    std::vector<int> numbers;   // acquire memory
    numbers.push_back(42);      // use resource
    // cleanup happens automatically when numbers goes out of scope
}
Enter fullscreen mode Exit fullscreen mode

Even better: RAII plays nicely with exceptions.

void safe_function() {
    std::ifstream file("data.txt");
    std::vector<int> buffer(1024);

    // Resources freed automatically,
    // even if we return early or throw.
}
Enter fullscreen mode Exit fullscreen mode

RAII in Structs

C++ doesn’t care if you use struct or class, the only difference is default visibility (public for structs vs private). Both can do RAII.

struct FileHandler {
    std::unique_ptr<std::fstream> file;

    FileHandler(const std::string& filename) {
        file = std::make_unique<std::fstream>(filename, std::ios::in | std::ios::out);
        if (!file->is_open()) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~FileHandler() {
        if (file && file->is_open()) {
            file->close();
        }
    }

    FileHandler(const FileHandler&) = delete;
    FileHandler& operator=(const FileHandler&) = delete;

    FileHandler(FileHandler&&) = default;
    FileHandler& operator=(FileHandler&&) = default;

    void write(const std::string& content) {
        if (file) *file << content;
    }
};
Enter fullscreen mode Exit fullscreen mode

Wrap Up

RAII is the beating heart of C++:

  • Resources tied to object lifetimes.
  • Automatic cleanup through constructors/destructors.
  • The Rule of Five to control creation, destruction, copying, and moving.
  • Smart pointers for ownership.
  • Built-in guarantees, even with exceptions.

Adopt RAII early and your C++ journey will get way easier.

Top comments (0)