raii要求构造函数必须成功获取资源,否则抛异常;析构函数仅安全释放资源且禁止抛异常;需显式删除拷贝、实现移动语义;优先用std::unique_ptr封装简单资源。

构造函数里必须完成资源获取,否则对象不合法
RAII不是“尽量在构造里拿资源”,而是“拿不到资源就别让对象存在”。比如打开文件失败、申请内存失败、加锁超时,这些都该在构造函数里抛异常,而不是返回错误码或设个 is_valid_ 标志位。
常见错误是把资源获取拆成两步:FileHandler fh; fh.open("a.txt"); —— 这退化成C风格管理,open() 可能被漏调,或者调两次,或者异常后没清理。
- 构造函数里调
fopen、socket、pthread_mutex_init、new等,失败立即 throw - 析构函数只做释放,不做任何可能失败的检查(比如不重试关闭)
- 资源句柄初始化为无效值(如
fd_ = -1、file_ = nullptr),析构前先判空
析构函数禁止抛异常,且必须能处理重复释放
如果 fclose(file_) 在析构里失败并 throw,而此时正因另一个异常栈展开,程序直接调用 std::terminate。所以析构函数里所有释放操作都要容忍失败。
更隐蔽的问题是:资源可能已被外部提前释放(比如用户误调了 close_fd(fd_)),这时你的析构再关一次就会 UB 或 crash。
- 析构中对资源句柄做有效性检查:
if (fd_ != -1) { close(fd_); fd_ = -1; } - 释放后立即将句柄置为无效值,避免二次释放
- 不要在析构里调用可能抛异常的函数(包括
std::cout、std::string构造等)
拷贝和移动语义必须显式控制
原始资源(文件描述符、互斥锁、socket)几乎都不支持拷贝。但 C++ 默认会合成拷贝构造函数和拷贝赋值运算符——如果不干预,两个对象会持有同一个 fd_,析构时 close 两次,触发 double-close 错误。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
移动语义则必须接管资源并清空源对象状态,否则移动后原对象析构仍会释放已转移的资源。
- 禁止拷贝:
MyResource(const MyResource&) = delete;和MyResource& operator=(const MyResource&) = delete; - 实现移动构造:
MyResource(MyResource&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } - 移动赋值同理,注意自赋值安全(先保存原值,再清理,再接管)
用 std::unique_ptr 封装 C 风格资源最省事
自己写 RAII 类容易漏掉移动语义或异常安全细节。对简单资源(FILE*、HANDLE、void* 分配的内存),优先用 std::unique_ptr + 自定义删除器。
它自带移动语义、禁止拷贝、自动调用删除器,且无需写类定义。
std::unique_ptr<file decltype> fp(fopen("a.txt", "r"), &fclose);</file>- 删除器可以是 lambda:
std::unique_ptr<int void> arr(new int[10], [](int* p) { delete[] p; });</int> - 注意:
std::shared_ptr有原子开销和额外堆分配,除非真需要共享所有权,否则别用
真正难的不是“怎么写析构函数”,而是判断哪些资源值得封装、哪些边界条件必须覆盖(比如 fork 后 fd 是否继承、信号中断对 read() 的影响是否要重试)。这些没法靠模板解决,得结合具体系统行为来设计。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










