RAII全称Resource Acquisition Is Initialization,即“资源获取即初始化”。这种技术的核心思想是,通过在对象的构造函数中获取资源,并在对象的析构函数中释放资源,来确保资源的正确管理。
它绑定了在使用前必须获取的资源的生命周期(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、 磁盘空间、数据库连接——任何有限供应的东西)到对象的生命周期。
RAII保证资源对任何可能访问对象的函数都可用(资源的可用性是类不变量,消除了冗余的运行时测试)。它还保证在控制对象的生命周期结束时,以获取的相反顺序释放所有资源。同样,如果资源获取失败(构造函数因异常而退出),则释放每个完全构造的成员和基础子对象获取的所有资源,释放顺序与初始化顺序相反。这利用了核心语言特性(对象生命周期,作用域退出,初始化顺序和堆栈展开),消除了资源泄漏并保证异常安全。这种技术的另一个名称是范围绑定资源管理(Scope-Bound Resource Management,SBRM),基本用例是由于作用域退出而结束RAII对象的生命周期。
RAII可以总结如下:
- 将每个资源封装到一个类中,其中
- 构造函数获取资源并建立所有类不变量,或者如果无法完成则抛出异常,
- 析构函数释放资源并且从不抛出异常;
- 总是通过 RAII 类的实例使用资源
- 具有自动存储持续时间或临时生命周期本身,或者
- 具有受自动或临时对象的生命周期限制的生命周期
Move语义使在对象之间、跨作用域以及线程内外安全地转移资源所有权成为可能,同时保持资源安全。
具有 open()/close()、lock()/unlock() 或 init()/copyFrom()/destroy() 成员函数的类是非 RAII 类的典型示例:
std::mutex m;
void bad()
{
m.lock(); // 获取mutex
f(); // 如果f()抛出一个异常,那么mutex永远都不会被释放
if(!everything_ok()) return; // 提前返回,mutex永远不会被释放
m.unlock(); // 如果执行到此语句,释放mutex
}
void good()
{
std::lock_guard<std::mutex> lk(m); // RAII class: mutex获取即初始化
f(); // 如果 f() 抛出一个异常,那么释放mutex
if(!everything_ok()) return; // 提前返回,mutex被释放。
// good()正常返回,mutex被释放
}
当定义一个RAII类时,需要在类的构造函数中获取资源,并在析构函数中释放资源。当RAII对象被创建时,资源会被自动获取,当对象被销毁时,资源会被自动释放。这样,即使程序出现异常,也能够保证资源的正确释放。
下面看一个使用RAII技术管理内存的例子:
class MyMemory {
public:
MyMemory(int size) : m_data(new int[size]) {}
~MyMemory() { delete[] m_data; }
private:
int* m_data;
};
int main() {
MyMemory mem(100);
// 使用mem对象管理的内存
return 0;
}
在这个例子中,我们定义了一个MyMemory类来管理一个动态分配的内存区域。在构造函数中,我们使用new运算符分配了一块内存,并将指针存储在m_data成员变量中。在析构函数中,我们使用delete[]运算符释放了这块内存。
在main函数中,我们创建了一个MyMemory对象,它会在作用域结束时自动被销毁。由于MyMemory对象的析构函数会释放内存,因此我们无需手动释放内存,RAII技术帮助我们避免了内存泄漏的可能性。
c++ standard library
管理自己资源的 C++ 库类遵循 RAII:std::string、std::vector、std::jthread(C++20 起),以及许多其他类在构造函数中获取它们的资源(错误时抛出异常), 在它们的析构函数(永远不会抛出)中释放它们,并且不需要显式清理。
此外,标准库提供了几个 RAII 包装器来管理用户提供的资源:
- std::unique_ptr 和 std::shared_ptr 管理动态分配的内存,或者使用用户提供的删除器,管理由普通指针表示的任何资源;
- std::lock_guard、std::unique_lock、std::shared_lock 来管理互斥锁。
Notes
RAII 不适用于对使用前未获取资源的管理:CPU 时间、内核和缓存容量、熵池容量、网络带宽、电力消耗、堆栈内存。
1.在析构函数中宏释放资源,如果发生异常,那么还会执行析构函数吗?
看一个例子:
class Obj
{
public:
Obj() {std::cout << "Obj()" << std::endl;}
~Obj() { std::cout << "~Obj()" << std::endl; }
};
void foo(int n)
{
Obj obj;
if (n == 66) {
throw "throw an exception";
}
}
int main()
{
try {
foo(67);
foo(66);
} catch (const char *s) {
std::cout << s << std::endl;
}
return 0;
}
我们可以看到输出如下:
Obj()
~Obj()
Obj()
~Obj()
throw an exception
也就是说,即使发生异常,析构函数也会被执行。
参考:
https://en.cppreference.com/w/cpp/language/raii
本文来自博客园,原创作者:Clemens,转载请注明原文链接:https://www.cnblogs.com/errorman/p/17254732.html