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