RAII是C++中唯一能保证资源不泄漏的机制;构造函数必须成功获取资源或抛异常,禁止半初始化;析构函数必须noexcept,仅安全释放并置空句柄。

RAII 不是“可选技巧”,而是 C++ 中唯一能保证资源不泄漏的机制;手动 delete、fclose 或靠 try-catch 收尾,只要存在提前 return、异常或 goto,就必然漏释放。
构造函数必须完成资源获取,失败就抛异常
构造函数里不能只“尝试”获取资源,也不能留一个“半初始化”对象。比如打开文件失败时返回 -1,但没抛异常,那对象就处于无效状态,后续析构可能误关无效句柄,甚至 crash。
- 资源获取失败必须抛出异常(如
std::system_error),让对象根本构造不成功 - 不要在构造函数里做复杂逻辑(如重试、日志),它只干一件事:拿到资源,否则就滚
- 若资源类型本身不支持异常(如 C 风格 API),需用
errno检查后显式 throw
示例:FileHandle 构造中 open() 失败立即 throw:
explicit FileHandle(const char* path) {
fd_ = open(path, O_RDONLY);
if (fd_ == -1) throw std::system_error{errno, std::generic_category()};
}
析构函数必须 noexcept 且只做释放
析构函数一旦抛异常,在栈展开过程中会直接调用 std::terminate——程序当场终止,比资源泄漏更致命。
- 所有释放操作都得包裹在
if判空逻辑里(如if (fd_ != -1) close(fd_);) - 释放后把句柄置为无效值(如
-1或nullptr),避免二次释放 - 禁止在析构里做任何可能抛异常的操作:不 new、不 log、不网络调用、不格式化字符串
错误写法:~FileHandle() { std::cout —— std::cout 可能抛 std::ios_base::failure。
拷贝必须禁用,移动必须转移所有权
两个对象管同一份资源(如两个 FileHandle 持有同一个 fd),析构时会 double-close,Linux 下触发 EBADF,Windows 下可能静默失败但破坏内核句柄表。
- 拷贝构造和拷贝赋值一律设为
= delete - 移动构造/赋值要明确“掏空”源对象:把
fd_移走,并将源设为-1 - 移动操作必须标
noexcept,否则容器(如std::vector)扩容时可能拒绝移动而退化为拷贝
关键代码片段:
FileHandle(FileHandle&& other) noexcept : fd_(other.fd_) {
other.fd_ = -1;
}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (fd_ != -1) close(fd_);
fd_ = other.fd_;
other.fd_ = -1;
}
return *this;
}
优先复用标准库 RAII 类型,而非手写
手写 RAII 容易漏掉移动语义、noexcept、判空等细节,而标准库类型已通过多年压测验证。除非封装的是标准库未覆盖的资源(如自定义硬件句柄、特定协议 socket),否则别重复造轮子。
- 内存 → 用
std::unique_ptr或std::vector,不是裸new - 文件 → 用
std::ifstream/std::ofstream,不是fopen+手写类 - 锁 → 用
std::lock_guard或std::unique_lock,不是pthread_mutex_lock - 自定义资源若必须手写,务必检查五法则(拷贝/移动/析构/赋值)是否完整实现
一个常被忽略的点:std::unique_ptr 的自定义删除器如果带状态(比如需要传参的 lambda),就不能用于数组类型推导,必须显式指定类型,否则编译失败。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











