Skip to content

Wiki

RAII

About 8 min read

Existing English translations were reused. 2 current source blocks remain in Simplified Chinese, without triggering paid translation.

1.What problem does this section solve?

In C, we often see code like this:

FILE* f = fopen("data.txt", "r");
// ... 使用文件 ...
fclose(f);  // 容易忘记!

If there is an early return in the middle, or an exception is thrown,fclose will not be executed, causing a resource leak. Similar problems also occur with memory (malloc/free), locks (lock/unlock), sockets, and all other resources that require "acquire-release".

RAII leverages the determinism of C++ object lifetimes,automatically managing resourcesso that you no longer need to release them manually.

2.What is this feature?

RAII (Resource Acquisition Is Initialization) is the most important resource management idiom in C++:

  • constructorAcquire resources in the.
  • destructorRelease resources in the.
  • When an object goes out of scope, the destructordefinitelywill be called.

This is C++ one of the core design philosophies that distinguishes it from C and other languagescore design philosophy, smart pointers, lock_guard, and fstream are all implementations of RAII.

3.C++ Standard Edition

C++98 (RAII has existed since the birth of C++, and modern RAII tools such as smart pointers and lock_guard matured in C++11).

4.Required header files

RAII is a programming concept and does not require a specific header file. However, the implementation of RAII is scattered in various places:<memory>(smart pointers),<mutex>(locks),<fstream>(file streams), etc.

5.Basic syntax

class RAIIExample
{
    Resource* res;  // 管理的资源

public:
    RAIIExample() : res(获取资源) { }   // 构造:获取资源
    ~RAIIExample() { 释放资源; }        // 析构:释放资源
};

6.Common RAII implementations

RAII types managed resources header files
std::unique_ptr dynamic memory <memory>
std::shared_ptr dynamic memory (shared) <memory>
std::lock_guard mutex <mutex>
std::unique_lock mutex (flexible) <mutex>
std::fstream file handle <fstream>
std::thread thread (requires join/detach) <thread>

7.Example code

7.1.Example 1: Problems without RAII vs. with RAII

#include <iostream>

// 模拟一个资源
class Resource
{
public:
    void open()  { std::cout << "Resource opened\n"; }
    void close() { std::cout << "Resource closed\n"; }
    void use()   { std::cout << "Resource used\n"; }
};

// ❌ 手动管理:容易忘记 close
void no_raii()
{
    Resource r;
    r.open();
    r.use();
    // 如果这里抛异常或提前 return,close 不会执行!
    r.close();
}

// ✅ RAII:利用析构函数自动释放
class ResourceGuard
{
    Resource& r;
public:
    ResourceGuard(Resource& res) : r(res)
    {
        r.open();  // 构造时获取资源
    }
    ~ResourceGuard()
    {
        r.close(); // 析构时释放资源
    }
};

void with_raii()
{
    Resource r;
    ResourceGuard guard(r);  // 构造时 open
    r.use();
    // guard 离开作用域,析构函数自动 close
}

int main()
{
    std::cout << "=== no_raii ===\n";
    no_raii();

    std::cout << "\n=== with_raii ===\n";
    with_raii();

    return 0;
}

Running result:

=== no_raii ===
Resource opened
Resource used
Resource closed

=== with_raii ===
Resource opened
Resource used
Resource closed

7.2.Example 2: Building on Example 1, use lock_guard to understand RAII lock management

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;  // 共享互斥量
int counter = 0;

// ❌ 手动加锁解锁(容易出问题)
void manual_lock()
{
    mtx.lock();
    ++counter;
    // 如果这里抛异常,unlock 永远不会被执行!
    mtx.unlock();
}

// ✅ RAII 风格:lock_guard 自动管理锁
void raii_lock()
{
    std::lock_guard<std::mutex> lock(mtx);  // 构造时 lock
    ++counter;
    // lock 离开作用域,析构函数自动 unlock
}

int main()
{
    std::thread t1([] {
        for (int i = 0; i < 1000; ++i)
            raii_lock();
    });
    std::thread t2([] {
        for (int i = 0; i < 1000; ++i)
            raii_lock();
    });

    t1.join();
    t2.join();

    std::cout << "counter = " << counter << " (expected 2000)\n";

    return 0;
}

Running result:

counter = 2000 (expected 2000)

7.3.Example 3: Building on Example 2, use fstream to understand file RAII

#include <iostream>
#include <fstream>
#include <string>

// RAII:fstream 在析构时会自动关闭文件
void write_and_read()
{
    // 写文件
    {
        std::ofstream out("raii_test.txt");
        out << "Hello RAII!\n";
        out << "This is line 2.\n";
        // out 离开作用域,文件自动关闭
    }

    // 读文件
    {
        std::ifstream in("raii_test.txt");
        std::string line;
        while (std::getline(in, line))
        {
            std::cout << line << "\n";
        }
        // in 离开作用域,文件自动关闭
    }
}

int main()
{
    write_and_read();
    std::cout << "File was automatically closed by RAII\n";
    return 0;
}

Running result:

Hello RAII!
This is line 2.
File was automatically closed by RAII

7.4.Example 4: Building on Example 3, the value of RAII becomes apparent only when returning early

The following example deliberately simulates "discovering invalid data halfway through processing and returning early." In the normal flow, manual release and RAII may look similar, but once there are multiple return paths, the difference becomes obvious.

#include <iostream>
#include <string>
#include <utility>
#include <vector>

class Connection
{
    std::string name_;

public:
    explicit Connection(std::string name) : name_(std::move(name))
    {
        std::cout << name_ << " connected\n";
    }

    ~Connection()
    {
        std::cout << name_ << " disconnected\n";
    }

    void send(const std::string& msg)
    {
        std::cout << name_ << " send: " << msg << "\n";
    }
};

bool upload_with_raii(const std::vector<std::string>& lines)
{
    Connection conn("server"); // 构造时连接,函数结束时自动断开

    for (const auto& line : lines)
    {
        if (line.empty())
        {
            std::cout << "empty line, stop upload\n";
            return false; // conn 仍然会析构
        }

        conn.send(line);
    }

    return true; // conn 也会析构
}

int main()
{
    std::vector<std::string> data = {"hello", "world", "", "after error"};

    bool ok = upload_with_raii(data);
    std::cout << "upload ok = " << std::boolalpha << ok << "\n";

    return 0;
}

Running result:

server connected
server send: hello
server send: world
empty line, stop upload
server disconnected
upload ok = false

8.Operation result

See the "run results" for each example above.

9.Key syntax explanation in the example

示例 讲了什么 新出现的语法 为什么这样写 注意事项
示例 1 RAII 基本原理 构造函数获取,析构函数释放 展示了手动管理的问题和 RAII 的解决方案 RAII 的"一定执行析构"是 C++ 的核心保证
示例 2 lock_guard 是 RAII std::lock_guard<std::mutex> 构造时加锁,析构时解锁,异常安全 比手动 lock/unlock 安全得多
示例 3 fstream 是 RAII std::ofstream、std::ifstream 构造时打开文件,析构时关闭文件 不需要显式写 close
示例 4 多返回路径中的 RAII 构造/析构、提前 return 真实工程里经常有提前返回,RAII 能保证资源仍被释放 资源对象要放在正确的作用域里

10.Common Errors

Error 1: Throwing an exception in a destructor

~MyRAII()
{
    cleanup();  // 如果 cleanup 抛异常...
}

If a destructor throws an exception while another exception is already propagating, the program will directly std::terminate. Destructors should be marked noexcept and catch all exceptions.

Mistake 2: Creating RAII objects on the heap

auto* guard = new std::lock_guard<std::mutex>(mtx);  // ❌ 永远不会自动析构!

Correct approach: RAII objects must be created on the stack to take advantage of automatic destruction when leaving the scope.

Error 3: Forgetting the scope of RAII objects

void func()
{
    std::lock_guard<std::mutex> lock(mtx);
    // 锁在这里面生效
}  // 离开作用域,解锁

// 在外面访问共享数据没有保护!

Correct approach: Ensure that shared data is accessed within the scope of the lock.

11.Usage Recommendations

  1. "需要配对的获取/释放"就用 RAII:这是 C++ 的资源管理第一原则。
  2. 永远不要手动 new/delete、lock/unlock、open/close:用智能指针、lock_guard、fstream。
  3. RAII 对象必须在栈上:利用作用域自动触发析构。
  4. 析构函数永远不要抛异常:标记 noexcept。
  5. 理解 RAII 就理解了 C++ 的核心设计哲学:后续智能指针、并发编程都建立在 RAII 之上。
  6. 用作用域控制资源持有时间:想早点释放资源,就把 RAII 对象放进更小的 {} 作用域。

12.Summary

  • RAII = Resource Acquisition Is Initialization, and is the most core resource management idiom in C++.
  • Acquire the resource during construction, release the resource during destruction, and ensure release when leaving the scope.
  • std::unique_ptrandstd::lock_guardandstd::fstream are all RAII.
  • Multiple returnand under exceptions and complex branches, the value of RAII is most obvious.
  • Never throw exceptions from a destructor.
  • Once you understand RAII, you have laid a solid foundation for understanding smart pointers and concurrent programming.