RAII

Resource Acquisition Is Initialization

๐Ÿ” What is RAII?

RAII (Resource Acquisition Is Initialization) is a C++ programming technique where resource management is tied to object lifetime, ensuring automatic cleanup and exception safety.


class FileHandler {
    FILE* file;
public:
    FileHandler(const char* name) : file(fopen(name, "r")) {}
    ~FileHandler() { if(file) fclose(file); } // Automatic cleanup
};
                                    

Result:

File automatically closed when object is destroyed

RAII Principles

๐Ÿ—๏ธ

Acquire in Constructor

Get resources when object is created

Resource() { acquire(); }
๐Ÿงน

Release in Destructor

Free resources when object is destroyed

~Resource() { release(); }
โšก

Exception Safety

Guaranteed cleanup even with exceptions

// Always cleaned up
๐Ÿ”„

Automatic Management

No manual resource tracking needed

// No forget to cleanup

๐Ÿ”น File Management Example

RAII (Resource Acquisition Is Initialization) ensures files are automatically closed when they go out of scope, even if exceptions occur. By wrapping file handles in classes like std::fstream, the destructor guarantees cleanup. For example, opening a file in a constructor and closing it in the destructor prevents resource leaks. This pattern is fundamental to exception-safe C++, simplifying resource management and eliminating manual cleanup calls, which reduces bugs and improves code reliability.

#include <iostream>
#include <fstream>
#include <stdexcept>

class SafeFile {
private:
    std::ifstream file;
public:
    SafeFile(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            throw std::runtime_error("Cannot open file");
        }
        std::cout << "File opened successfully\n";
    }
    
    ~SafeFile() {
        if (file.is_open()) {
            file.close();
            std::cout << "File closed automatically\n";
        }
    }
    
    std::string readLine() {
        std::string line;
        std::getline(file, line);
        return line;
    }
};

int main() {
    try {
        SafeFile myFile("data.txt");
        // File is automatically closed when myFile goes out of scope
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }
    return 0;
}

Output:

File opened successfully
File closed automatically

๐Ÿ”น Memory Management Example

RAII manages dynamic memory automatically using smart pointers like std::unique_ptr, preventing leaks and ensuring timely deallocation. When a unique_ptr goes out of scope, its destructor frees the associated memory. For example, auto arr = std::make_unique<int[]>(5); allocates an array that is automatically freed. This eliminates manual delete calls, enhances exception safety, and makes memory management deterministic, which is crucial for robust, maintainable applications.

#include <iostream>

class IntArray {
private:
    int* data;
    size_t size;
    
public:
    IntArray(size_t n) : size(n), data(new int[n]) {
        std::cout << "Array of " << size << " integers allocated\n";
        for (size_t i = 0; i < size; ++i) {
            data[i] = i * 10;
        }
    }
    
    ~IntArray() {
        delete[] data;
        std::cout << "Array memory freed automatically\n";
    }
    
    int& operator[](size_t index) {
        return data[index];
    }
    
    size_t getSize() const { return size; }
};

int main() {
    {
        IntArray arr(5);
        std::cout << "arr[2] = " << arr[2] << std::endl;
    } // arr destructor called here automatically
    
    std::cout << "Program continues...\n";
    return 0;
}

Output:

Array of 5 integers allocated
arr[2] = 20
Array memory freed automatically
Program continues...

๐Ÿ”น Lock Management Example

RAII simplifies thread synchronization by automatically acquiring and releasing locks using classes like std::lock_guard. The lock is acquired in the constructor and released in the destructor, ensuring mutual exclusion even during exceptions. For example, std::lock_guard lock(mtx); protects a critical section. This pattern prevents deadlocks from forgotten unlocks and makes multithreaded code safer and cleaner, which is essential for concurrent programming.

#include <iostream>
#include <mutex>

class ThreadSafeCounter {
private:
    int count = 0;
    mutable std::mutex mtx;
    
public:
    void increment() {
        std::lock_guard<std::mutex> lock(mtx); // RAII lock
        ++count;
        std::cout << "Count incremented to: " << count << std::endl;
        // lock automatically released when lock goes out of scope
    }
    
    int getValue() const {
        std::lock_guard<std::mutex> lock(mtx); // RAII lock
        return count;
        // lock automatically released
    }
};

int main() {
    ThreadSafeCounter counter;
    counter.increment();
    counter.increment();
    std::cout << "Final count: " << counter.getValue() << std::endl;
    return 0;
}

Output:

Count incremented to: 1
Count incremented to: 2
Final count: 2

๐Ÿง  Test Your Knowledge

When are resources released in RAII?