Logo成贤计协指南

RAII

把资源管理和对象生命周期绑在一起。

在基础篇里,我们其实已经好几次碰到过 RAII,只是当时还没有给它起名字。在之前的学习中,你已经知道了:

  • 局部对象离开作用域时会被销毁。
  • 析构函数可以在对象销毁时自动执行清理逻辑。
  • 智能指针离开作用域时会自动释放内存。

这三件事连起来,就组成了现代 C++ 里非常核心的一种思路:RAII (Resource Acquisition Is Initialization),即 “资源获取即初始化”。(啊不过应该没什么人叫中文名吧,要不是我写教程查资料我都不知道他中文是这个💦)

这个名字第一次看有点拗口。你可以大致理解为把资源绑定到对象身上。当对象存活时,资源由他来管理;当对象销毁时,资源就自动释放。

为什么会需要 RAII?

先看一个你已经很熟悉的问题:

void process() {
    int* data = new int[100];

    // ... 一些复杂的逻辑 ...
    if (some_error) {
        return; // ❌ 提前返回,delete[] 被跳过了
    }

    delete[] data;
}

这段代码的问题并不是你不会写 delete[],而是只要代码一复杂,人就很容易忘记在每一条退出路径上释放资源

现在这里只有一个 return,看起来还算容易检查。可一旦函数里出现更多分支、循环,或者以后你碰到了异常机制,这种“手动记得收尾”的写法就会越来越脆弱。

RAII 想解决的,就是这个问题:不要把“释放资源”这件事交给人的记忆力,而是交给对象的生命周期。

RAII 到底在做什么?

RAII 不是某一个语法,也不是某一个标准库类,而是一种设计方法:

  1. 在对象创建时拿到资源。
  2. 在对象销毁时释放资源。
  3. 让“释放资源”随着作用域自动发生,而不是靠手写收尾代码。

这里的“资源”不只是内存,还可以是:

  • 堆内存
  • 文件句柄
  • 网络连接
  • 数据库连接
  • 锁(未来在异步编程当中会接触到)

先看一个更直观的例子:文件

如果使用需要手动关闭的文件接口,代码往往会长这样:

void write_log() {
    FILE* file = fopen("log.txt", "w");
    if (file == nullptr) {
        return;
    }

    fprintf(file, "start\n");

    if (some_error) {
        return; // ❌ 忘记 fclose(file)
    }

    fprintf(file, "finish\n");
    fclose(file);
}

一旦中途 return,关闭动作就可能被跳过,导致内存泄漏。

在现代 C++ 里,更常见的做法是直接让一个对象来管理文件:

void write_log() {
    std::ofstream file("log.txt"); // 创建对象时打开文件
    if (!file.is_open()) {
        return;
    }

    file << "start\n";

    if (some_error) {
        return; // ✅ 没关系,离开作用域时会自动关闭
    }

    file << "finish\n";
} // file 在这里析构,自动关闭文件

这里的 std::ofstream 就是一个标准库提供的 RAII 对象。你不需要在函数最后“记得写一个关闭动作”,因为对象销毁时会自动替你完成。

这就是 RAII 最核心的价值:资源释放不再依赖“别忘了”,而是依赖语言本身的生命周期规则。

自己实现 RAII

实现 RAII 的方法有很多。而在 C++ 中,我们通常会通过类的构造函数和析构函数来实现:

class Buffer {
private:
    int* data;

public:
    Buffer(int size) {
        data = new int[size]; // 构造时获取资源
    }

    ~Buffer() {
        delete[] data; // 析构时释放资源
    }

    int* get() {
        return data;
    }
};

使用时就会变成:

void process() {
    Buffer data(100);

    // ... 使用 data.get() ...
    if (some_error) {
        return; // ✅ 依然安全,data 会自动析构
    }
}

这个例子里,Buffer 做的事情其实很朴素:

  • 构造函数里申请内存
  • 析构函数里释放内存
  • 把“申请”和“释放”绑成一个整体

这就是 RAII 的骨架。

当然,在实际开发中,你通常不需要自己去写这种裸 new/delete 的封装,因为标准库已经提供了更成熟的方案,比如 std::vectorstd::stringstd::unique_ptr。这里自己写一个小类,只是为了让你看清它背后的思路。


基础篇里讲过的智能指针,其实就是 RAII 的典型应用:

std::unique_ptr<int> p(new int(42));

它帮管理一块堆内存,在生命周期结束时自动帮你释放这块内存。

Last updated on

On this page