raii要求资源必须在构造函数中获取并验证,析构函数中无条件、noexcept释放,禁止拷贝、慎用移动,确保异常安全与资源不泄漏。

构造函数里必须完成资源获取,不能留到后续调用
RAII 的前提是“资源获取即初始化”,意味着 constructor 必须真正拿到资源并验证有效性。如果只是存个指针或句柄但没实际申请,就不是 RAII——那是懒加载,容易在后续使用时才暴露失败,破坏异常安全保证。
- 文件类必须在
FileHandler(const char* path)中调用fopen()并检查返回值,不能只保存path - 锁包装器如
std::lock_guard在构造时立即调用mtx.lock(),否则无法确保临界区入口的原子性 - 动态内存类(如简易
MyPtr)应在构造中执行new,而非把裸指针传入后延迟分配 - 若资源获取可能失败(如网络连接超时),应在构造函数中抛出异常,而不是返回错误码——C++ 栈展开机制会保证已构造对象的析构,但不会回滚未完成的构造
析构函数必须 noexcept,且不能抛异常
~MyPtr() 这类析构函数一旦抛出异常,而此时又处于栈展开过程中(比如另一个对象构造失败正在析构),程序会直接调用 std::terminate() 终止,无法捕获或恢复。
- 所有 RAII 类的析构函数应显式声明为
noexcept,例如:~FileHandler() noexcept { if (file) fclose(file); } - 释放操作本身可能失败(如
fclose()返回EOF),但不应 throw;可记录日志或忽略,绝不能中断析构流程 - 标准库中所有 RAII 类型(
std::unique_ptr、std::lock_guard、std::fstream)的析构函数都是noexcept
拷贝必须禁用,移动需谨慎实现
资源通常不可共享或不可复制。允许拷贝会导致两个对象指向同一份资源,析构时重复释放——典型段错误来源。
- 一律删除拷贝构造和拷贝赋值:
MyPtr(const MyPtr&) = delete;、MyPtr& operator=(const MyPtr&) = delete; - 移动语义不是必须的,但若要支持(如返回局部 RAII 对象),必须将原对象资源置空:
other.ptr = nullptr;,否则移动后原对象析构仍会释放一次 - 注意:
std::unique_ptr支持移动,std::lock_guard不支持移动(设计上就是作用域绑定的瞬时锁)
资源类型决定 RAII 类的粒度与生命周期边界
不是所有资源都适合封装成独立 RAII 类。关键看「谁负责销毁」和「生命周期是否天然匹配作用域」。
- 堆内存 → 用
std::unique_ptr或std::shared_ptr,别手写,除非有特殊对齐/分配器需求 - 文件句柄 → 封装为
FileHandler可行,但更推荐直接用std::ifstream/std::ofstream,它们已完备实现 RAII - 互斥锁 →
std::lock_guard足够;需要延迟加锁或条件变量配合时,换std::unique_lock - 数据库连接、socket 等长生命周期资源 → 若需跨函数传递,应避免用栈对象封装,改用智能指针管理其 RAII 包装器,否则容易因提前析构断连
析构函数的执行时机是确定的,但它的行为边界非常窄:只做清理,不抛异常,不依赖其他对象状态。写 RAII 类时,最容易被忽略的不是“怎么拿资源”,而是“怎么安全地、静默地交还它”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











