RAII的本质是构造函数获取资源、析构函数释放资源;它是利用对象生命周期自动管理资源的惯用法,要求资源获取在构造函数中、释放在noexcept析构函数中,优先使用std::unique_ptr等智能指针并自定义删除器处理非内存资源。

RAII的本质是构造函数获取资源、析构函数释放资源
RAII不是语法特性,而是C++中利用对象生命周期自动管理资源的惯用法。核心在于:资源获取(如 new、fopen、pthread_mutex_init)必须放在构造函数里,资源释放(如 delete、fclose、pthread_mutex_destroy)必须放在析构函数里,且析构函数必须是 noexcept(否则异常途中栈展开失败会直接调用 std::terminate)。
常见错误是把资源指针裸露在外,或在析构中抛出异常:
class BadFileHandle {
FILE* fp;
public:
BadFileHandle(const char* name) { fp = fopen(name, "r"); }
~BadFileHandle() { if (fp) fclose(fp); } // ❌ 未检查 fclose 是否失败,且没声明 noexcept
};std::unique_ptr 和 std::shared_ptr 已覆盖绝大多数场景
手写 RAII 类容易出错,优先用标准智能指针。它们默认行为就是 RAII:构造时接管原始指针,析构时自动调用删除器。
-
std::unique_ptr适合独占资源(如堆内存、文件描述符),移动语义明确,零开销 -
std::shared_ptr适合共享所有权(如缓存对象),但有引用计数开销和循环引用风险 - 对非内存资源(如文件句柄、锁),必须自定义删除器:
std::unique_ptr<int decltype></int>或 lambda
示例——安全封装 POSIX 文件描述符:
立即学习“C++免费学习笔记(深入)”;
auto fd_deleter = [](int fd) { if (fd >= 0) ::close(fd); };
using unique_fd = std::unique_ptr<int, decltype(fd_deleter)>;
unique_fd make_unique_fd(const char* path) {
int fd = open(path, O_RDONLY);
return fd < 0 ? unique_fd(nullptr, fd_deleter) : unique_fd(new int(fd), fd_deleter);
}自定义 RAII 类必须禁用拷贝、显式定义移动(如果需要)
资源通常不可复制(比如不能有两个对象同时“拥有”同一个 FILE*)。因此:
- 显式删除拷贝构造函数和拷贝赋值运算符:
MyRAII(const MyRAII&) = delete;、operator=(const MyRAII&) = delete; - 若需转移所有权,必须实现移动构造函数和移动赋值运算符,并将原对象资源置为无效状态(如指针设为
nullptr) - 所有成员变量也应满足 RAII 原则(例如内部持有
std::unique_ptr而非裸指针)
漏掉拷贝禁用,编译器会自动生成浅拷贝,导致双重释放 —— 这是最常见的崩溃根源之一。
RAII 不解决所有资源问题:线程局部、全局初始化、跨 DLL 边界要格外小心
RAII 依赖对象生命周期,而某些资源的生命周期不匹配 C++ 对象模型:
- 静态/全局对象的构造顺序不确定,析构顺序与构造相反但跨编译单元不可控 → 避免在全局对象构造函数中依赖其他全局对象持有的资源
- 线程局部存储(
thread_local)对象的析构发生在线程退出时,若线程被pthread_cancel或 WindowsTerminateThread强制结束,析构函数可能根本不执行 - Windows DLL 中的 RAII 对象,若 DLL 被
FreeLibrary卸载,其全局对象析构可能发生在运行时已部分销毁之后 → 应避免在 DLL 全局作用域使用复杂 RAII
这些边界情况不会报错,但会在特定部署环境下静默失效 —— 很难复现,调试成本极高。


















