RAII是C++通过构造函数获取资源、析构函数释放资源的强制约定,依赖栈对象或智能指针确保析构确定执行;裸指针无法保障异常安全与自动清理,析构函数必须完整正确且配合移动语义避免重复释放。

RAII 不是语法糖,也不是库功能,它是 C++ 用构造函数和析构函数绑定资源生命周期的强制约定。只要你定义了类,并在构造函数里获取资源(比如 new、fopen、pthread_mutex_init),在析构函数里释放(delete、fclose、pthread_mutex_destroy),就天然符合 RAII。
为什么必须用栈对象或智能指针,不能裸指针 new 出来?
裸指针无法保证析构执行:异常抛出、提前 return、忘记 delete 都会导致资源泄漏。RAII 的核心保障来自 C++ 对栈对象生命周期的确定性管理——作用域结束时自动调用析构函数。
- 栈对象:最直接,
std::ifstream f("a.txt");离开作用域自动关闭文件 -
std::unique_ptr/std::shared_ptr:堆上资源也能享受 RAII,前提是自定义删除器(例如std::unique_ptr) - 绝不能写
FILE* f = fopen(...);然后靠人工配对fclose—— 这已经脱离 RAII
常见误用:把 RAII 当成“只要写了析构函数就行”
析构函数里没做清理,或者清理逻辑有缺陷(比如没检查空指针、忽略返回值、未处理部分失败),照样泄漏或崩溃。RAII 的有效性完全依赖析构函数的正确性和完整性。
- 错误示例:
class BadHandle { int fd_; public: BadHandle(const char* path) { fd_ = open(path, O_RDONLY); } ~BadHandle() { /* 忘了 close(fd_) */ } // 资源泄漏 }; - 正确示例:
class FileHandle { int fd_; public: FileHandle(const char* path) : fd_(open(path, O_RDONLY)) { if (fd_ == -1) throw std::system_error(errno, std::generic_category()); } ~FileHandle() { if (fd_ != -1) close(fd_); } FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete; };
RAII 和 move 语义怎么配合?
资源只能归属一个所有者。如果类支持移动(比如 std::vector、std::unique_ptr),必须显式禁用拷贝、实现移动构造/赋值,并在移动后将原对象资源置为无效状态(如把 fd_ 设为 -1),否则析构时重复释放会 crash。
立即学习“C++免费学习笔记(深入)”;
- 移动后不置空
fd_→ 两个对象析构都调close(-1)或更糟的close(已关闭的 fd) - 没删除拷贝构造函数 → 可能意外触发浅拷贝,导致双析构
-
标准库类型(如
std::mutex)本身不可拷贝不可移动,就是刻意为之的设计
RAII 看似简单,真正落地时最难的是边界情况:异常安全、移动后的状态一致性、多线程下析构是否可重入。别只盯着“有没有析构函数”,要盯住“析构函数会不会被跳过、会不会被重复调、会不会在错误状态下被调”。








