RAII
简述
全称资源获取即初始化(英语:Resource Acquisition Is Initialization)
RAII要求,资源的有效期与持有资源的对象生命周期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄漏问题。
(摘自Wikipedia)
简单来说,资源与对象深度绑定 ——
- 对象创建,资源创建
- 对象销毁,资源释放
简单例子,C++中的lock_guard
C++
std::mutex m;
void foo()
{
// 创建 lock_guard 对象,直接上锁
std::lock_guard<std::mutex> lock(m);
}
// 当函数结束的时候,或者遇到异常退出等情况(离开作用域)
// 执行 lock_guard 的析构函数
// 自动解锁,有效避免了死锁问题例子
C++
class File {
public:
File(const char* name) {
f = fopen(name, "r");
}
~File() {
if (f) fclose(f);
}
FILE* get() const { return f; }
private:
FILE* f;
};
void foo() {
File file("data.txt"); // 构造 → 打开文件
// ...
} // 离开作用域 → 析构 → 自动 fclose以上封装了一个符合RAII规范的文件资源,保证了不会因为意外导致文件资源不关闭等
智能指针
C++ 中的
unique_ptrshared_ptrweak_ptr最经典的 RAII 其本质就是裸指针+元数据 —— 当只能指针执行析构函数的时候自动释放资源,三者的不同主要为所有权- 唯一所有者 ——
unique_ptr - 共享所有者 ——
shared_ptr - 非拥有观察者 ——
weak_ptr
unique_ptr
独占所有权,保证了某个对象在任意时刻只能被一个unique_ptr所拥有。 所以不能被拷贝,只能临时调用资源(不移交所有权),或者移动(移交所有权给其他unique_ptr)
示例
C++
#include <memory>
struct Foo {
Foo() { /* ... */ }
~Foo() { /* ... */ }
};
void basic_usage() {
// 创建:推荐用 make_unique
auto p1 = std::make_unique<Foo>();
// 移动所有权
auto p2 = std::move(p1); // p1 变为空,p2 拥有 Foo
// 访问
p2->some_method();
(*p2).some_method();
Foo* raw = p2.get();
// 释放:离开作用域自动 delete
} // p2 析构,Foo 被自动 deleteshared_ptr
共享所有权,也就是说一个对象可以同时被多个shared_ptr拥有。 在设计的使用,引入了一个计数器,被拥有时候计数器递增,然后再shared_ptr释放的时候计数器递减,直到计数器减为 0 的时候自动释放资源。
值得注意——计数器的增减操作是原子操作(线程安全),但是对于资源的访问本身不是线程安全,需要我们手动规范。
又因为多维护了计数器等东西,所以开销一定会更大。
DANGER
小心循环引用
如果两个shared_ptr相互引用,会产生死锁导致计数器永远不会降到0的情况,导致资源永远不会被释放。
weak_ptr
若所有权,“我有资源,但是所有权不是我的”,即不增加引用计数,只是“看着”一个由shared_ptr管理的对象
一般依附于shared_ptr产生,不参与生命周期管理