RAII
资源获取即初始化(Resource Acquisition Is Initialization),简称 RAII,是一种 C++ 编程技术[1][2],它将必须在使用前获取的资源(已分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥量、磁盘空间、数据库连接——任何供应有限的资源)的生命周期与对象的生命周期绑定。
RAII 确保资源对任何可以访问对象的函数都可用(资源可用性是类不变量,消除了冗余的运行时测试)。它还保证当控制其生命周期的对象生命周期结束时,所有资源都以获取的相反顺序释放。同样,如果资源获取失败(构造函数因异常而退出),则所有由每个完全构造的成员和基子对象获取的资源都以初始化顺序的相反顺序释放。这利用了核心语言特性(对象生命周期、作用域退出、初始化顺序和栈展开)来消除资源泄漏并保证异常安全。这种技术的另一个名称是作用域绑定资源管理(Scope-Bound Resource Management,SBRM),因为它最基本的使用场景是 RAII 对象的生命周期因作用域退出而结束。
RAII 可以总结如下:
- 将每个资源封装到一个类中,其中
- 构造函数获取资源并建立所有类不变量,如果无法完成则抛出异常,
- 析构函数释放资源,并且从不抛出异常;
- 始终通过 RAII 类的实例使用资源,该实例要么
- 具有自动存储期或临时生命周期,要么
- 其生命周期受自动或临时对象的生命周期限制。
移动语义支持在对象之间、容器内部和外部以及跨线程传输资源和所有权,同时确保资源安全。 |
(C++11 起) |
带有 open()
/close()
、lock()
/unlock()
或 init()
/copyFrom()
/destroy()
成员函数的类是非 RAII 类的典型示例。
std::mutex m; void bad() { m.lock(); // acquire the mutex f(); // if f() throws an exception, the mutex is never released if (!everything_ok()) return; // early return, the mutex is never released m.unlock(); // if bad() reaches this statement, the mutex is released } void good() { std::lock_guard<std::mutex> lk(m); // RAII class: mutex acquisition is initialization f(); // if f() throws an exception, the mutex is released if (!everything_ok()) return; // early return, the mutex is released } // if good() returns normally, the mutex is released
[编辑] 标准库
管理自身资源的 C++ 库类遵循 RAII:std::string、std::vector、std::jthread(C++20 起) 以及许多其他类都在构造函数中获取资源(并在出错时抛出异常),在析构函数中释放资源(从不抛出异常),并且不需要显式清理。
此外,标准库提供了几个 RAII 包装器来管理用户提供的资源:
|
(C++11 起) |
[编辑] 注意
RAII 不适用于管理那些在使用前未获取的资源:CPU 时间、核心可用性、缓存容量、熵池容量、网络带宽、电力消耗、栈内存。对于此类资源,C++ 类构造函数无法保证在对象生命周期内资源的可用性,必须使用其他资源管理方法。