raii封装资源时析构函数重复delete会崩溃,因裸指针无所有权语义,拷贝后多对象析构导致双重释放;必须禁用拷贝、支持移动、置空指针并确保析构幂等。

用 RAII 封装资源时,析构函数里重复 delete 会崩溃
直接在析构函数里写 delete ptr 是最常见也最危险的写法。一旦对象被拷贝(哪怕只是隐式发生),两个对象的析构函数都会执行同一句 delete,触发双重释放 —— 表现为段错误、堆损坏或静默数据破坏。
根本原因在于:裸指针不带所有权语义,无法自动标记“已释放”。RAII 要起作用,必须让资源的生命周期与对象绑定,且确保“释放动作只发生一次”。
- 禁止提供拷贝构造函数和拷贝赋值运算符(
= delete) - 如果需要转移资源,显式支持移动语义(
move构造/赋值) - 在析构函数中释放前,检查指针是否非空(
if (ptr) delete ptr;)只是缓解,不能根治——它掩盖了设计缺陷
std::unique_ptr 是默认安全解,但别手动 new + reset
std::unique_ptr 天然防止重复释放,因为它实现独占所有权:移动后源指针自动变为空(nullptr),再次调用 reset() 或析构都不会触发二次 delete。
但很多人误用:std::unique_ptr<int> p; p.reset(new int(42));</int> 看似没问题,实则埋雷——若 new 抛异常,p 还是空,没泄漏;但若构造函数里又抛异常,reset 之前的原始指针就丢了。
- 永远优先用
std::make_unique<t>(...)</t>,它原子完成内存分配和对象构造 - 避免裸
new后塞进智能指针,尤其不要在表达式中间调用reset - 自定义删除器时,确保删除函数本身是无异常的(
noexcept),否则析构可能中途终止,留下未清理状态
共享资源必须用 std::shared_ptr + std::weak_ptr 配合破循环
当多个对象需要共享同一块资源(比如观察者模式里的回调句柄),std::shared_ptr 是合理选择。但它自己不防循环引用:A 持有 B 的 shared_ptr,B 也持有 A 的 shared_ptr,引用计数永不归零,资源永远不释放。
这不是“重复释放”,而是“永不释放”,最终表现为内存泄漏。调试时容易误判为逻辑遗漏 delete,其实根本没机会走到那步。
- 只要存在双向持有关系,至少一端必须用
std::weak_ptr(比如 B 持有 A 的weak_ptr) - 使用前必须调用
lock()转成shared_ptr,并检查是否为空 ——weak_ptr不延长生命周期,只做临时访问 - 不要把
shared_ptr存在std::map或容器里再反复assign,频繁拷贝会意外增加引用计数,干扰释放时机
自定义 RAII 类里,析构函数必须能被异常安全地多次进入
这常被忽略:C++ 标准允许析构函数被调用多次(例如栈展开过程中异常传播),而你的析构函数如果没做防护,可能在第二次进入时对已释放资源再次操作。
典型错误是:在析构里 fclose(fp) 后没把 fp 设为 nullptr,下次调用又传给 fclose —— C 标准规定此时行为未定义,多数 libc 直接 abort。
- 所有资源句柄(
FILE*、HANDLE、int fd)释放后必须置为无效值(nullptr、INVALID_HANDLE_VALUE、-1) - 不要依赖“析构只调用一次”的假设,RAII 类的析构函数应幂等(idempotent)
- 若资源释放可能失败(如
close()返回 -1),记录日志但不要抛异常 —— 析构函数里抛异常会直接 terminate
unique_ptr 和 shared_ptr 提供所有权契约,但契约要靠用法来兑现。最容易被忽略的是:析构函数不是一次性收尾工作,它是异常传播链上可能被反复触达的临界点。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











