RAII的本质是构造函数获取资源、析构函数释放资源,是一种利用对象生命周期管理资源的C++编程范式,要求资源获取与构造绑定、释放与析构绑定,由编译器保证析构执行(非异常退出除外)。

RAII的本质是构造函数获取资源,析构函数释放资源
RAII不是语言特性,而是C++中利用对象生命周期管理资源的编程范式。核心在于:资源获取必须与对象构造绑定,资源释放必须与对象析构绑定——编译器保证析构函数一定会被调用(除非std::abort()或longjmp等非正常退出)。
常见错误是把资源操作写在普通成员函数里,比如open()和close()手动配对,这本质上退化成C风格资源管理,容易漏调close()或重复调用。
- 构造函数里做
new、fopen、pthread_mutex_init、std::lock_guard加锁等 - 析构函数里做
delete、fclose、pthread_mutex_destroy、自动解锁 - 禁止在析构函数中抛异常(否则可能引发
std::terminate) - 若需转移资源所有权(如移动语义),必须显式禁用拷贝或实现移动构造/赋值
std::unique_ptr和std::shared_ptr是RAII最常用的现成实现
智能指针不是“替代RAII”,它们本身就是RAII的经典封装。区别在于资源类型和生命周期策略:
-
std::unique_ptr<int>:独占所有权,移动后原指针变nullptr,析构时自动delete -
std::shared_ptr<int>:引用计数,最后一个shared_ptr析构时才释放资源 - 自定义删除器可适配任意资源,比如
std::unique_ptr<FILE, decltype(&fclose)> fp(fopen("a.txt", "r"), &fclose) - 注意
std::shared_ptr有轻微性能开销(原子计数、控制块内存分配),不要无脑替换unique_ptr
自定义RAII类时,必须处理好拷贝和移动语义
原始资源(如文件描述符、互斥锁、socket)通常不可复制,但C++默认生成拷贝构造函数和拷贝赋值运算符——如果不干预,会导致双释放或资源泄漏。
立即学习“C++免费学习笔记(深入)”;
- 禁止拷贝:在类内声明
MyResource(const MyResource&) = delete;和MyResource& operator=(const MyResource&) = delete; - 支持移动:实现移动构造函数,将
other.fd置为-1,当前对象接管该fd;移动赋值同理 - 若资源可复制(极少见),必须深拷贝底层资源(如
dup()系统调用复制fd),而非简单复制整数 - 析构函数要检查资源句柄是否有效(如
if (fd != -1) close(fd);),避免重复关闭
RAII不解决所有资源问题,要注意例外场景
RAII依赖栈对象或自动存储期对象的确定析构时机。以下情况它无法覆盖:
- 全局/静态对象的析构顺序不确定,两个RAII对象互相依赖时可能出错
- 异常构造函数中抛出:如果构造函数中途抛异常,已执行的资源获取步骤不会自动回滚(需在构造函数内用
try/catch手动清理) - 使用
new动态分配RAII对象但忘记delete,就退化为裸指针管理——此时应优先用std::make_unique - 信号处理函数中调用
longjmp或siglongjmp会绕过析构,POSIX环境下需格外小心
真正难的不是写一个LockGuard或FileGuard,而是判断哪些资源值得封装、哪些边界条件必须覆盖、以及什么时候该让步于更简单的方案(比如直接用std::vector代替手写内存池)。


















