RAII是C++通过构造函数获取资源、析构函数释放资源的强制约定,依赖栈对象或智能指针确保析构确定执行;裸指针无法保障异常安全与自动清理,析构函数必须完整正确且配合移动语义避免重复释放。
RAII 不是语法糖,也不是库功能,它是 C++ 用构造函数和析构函数绑定资源生命周期的强制约定。只要你定义了类,并在构造函数里获取资源(比如 new、fopen、pthread_mutex_init),在析构函数里释放(delete、fclose、pthread_mutex_destroy),就天然符合 RAII。
裸指针无法保证析构执行:异常抛出、提前 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 的有效性完全依赖析构函数的正确性和完整性。
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;
};资源只能归属一个所有者。如果类支持移动(比如 std::vector、std::unique_ptr),必须显式禁用拷贝、实现移动构造/赋值,并在移动后将原对象资源置为无效状态(如把 fd_ 设为 -1),否则析构时重复释放会 crash。
fd_ → 两个对象析构都调 close(-1) 或更糟的 close(已关闭的 fd)
如 std::mutex)本身不可拷贝不可移动,就是刻意为之的设计RAII 看似简单,真正落地时最难的是边界情况:异常安全、移动后的状态一致性、多线程下析构是否可重入。别只盯着“有没有析构函数”,要盯住“析构函数会不会被跳过、会不会被重复调、会不会在错误状态下被调”。