析构函数必须是public、noexcept、无参数;否则编译报错或行为未定义,且禁止抛异常,否则导致std::terminate。

析构函数必须是 public、noexcept、不带参数
如果你写了 ~MyClass() 却在类外调用它,或者加了参数、返回值、异常说明(比如没写 noexcept),编译器会报错或行为未定义。C++ 标准强制要求析构函数签名固定:~ClassName(),隐式 noexcept(true)。手动加上 noexcept 不仅更明确,还能避免被意外 noexcept 检查拒绝(比如放进 std::vector 时)。
常见错误现象:error: destructor cannot be declared to throw an exception(旧代码里写了 throw(...) 或漏了 noexcept);error: use of deleted function 'MyClass::~MyClass()'(私有析构导致无法自动销毁栈对象或容器元素)。
- 永远不要把析构函数设为
private或protected,除非你刻意设计成“只能通过智能指针管理”且已提供配套工厂函数 - 如果类内有
std::thread、FILE*、裸new出的内存等资源,析构函数里必须显式清理——不能指望“反正程序快结束了” - 别在析构函数里调用虚函数(包括
this->foo()),此时虚表已被部分销毁,行为未定义
RAII 类不该暴露裸指针或手动 delete
RAII 的核心不是“有析构函数”,而是“资源获取即初始化”——构造函数拿到资源,析构函数必然释放,中间不给用户绕过的机会。一旦你提供 get_raw_ptr() 或 release() 之类接口,就等于把资源生命周期控制权交出去了,RAII 就失效了。
使用场景:封装文件句柄、堆内存、锁、socket 连接。典型反例是自己写个 Buffer 类,构造用 new char[...],但还提供 data() 返回 char*,用户可能拿去传给 free() 或重复 delete[]。
立即学习“C++免费学习笔记(深入)”;
- 优先用
std::unique_ptr、std::shared_ptr管理堆内存,而不是手写析构函数做delete[] - 如果真要自定义资源类(比如封装
mmap),构造函数必须完成映射 + 检查失败,析构函数必须调用munmap,且禁止提供任何可导出底层地址的接口 - 注意移动语义:若类支持移动(如
Buffer(Buffer&&)),移动后原对象状态必须是“可析构但不可用”,析构函数仍要安全处理空状态(比如检查ptr_ != nullptr再delete[])
析构函数里禁止抛异常
这是 C++ 最硬的规则之一:~T() 默认是 noexcept,如果它抛了异常且此时栈正在展开(比如另一个异常还没处理完),程序直接调用 std::terminate() 终止,连 catch(...) 都救不了。
常见错误现象:std::terminate called without an active exception(实际是析构时抛异常触发的);日志里只看到进程 crash,没留下任何异常信息。
- 所有可能失败的操作(如
fclose()、close()、网络 flush)必须在析构函数里吞掉错误,或记录日志但绝不 throw - 如果资源释放逻辑复杂到可能出错(比如需要重试的磁盘刷写),考虑把释放动作拆成显式
shutdown()方法,让用户在 try/catch 中调用,析构函数只做兜底(比如 log warning) - 用
static_assert(noexcept(std::declval<t>().~T()), "...")</t>可在编译期检查类是否满足 noexcept 析构(适合模板库)
std::vector<:unique_ptr>> 不需要自定义析构函数
很多初学者以为“我要管理一堆对象,所以得写个析构函数遍历 delete”,其实只要用对标准容器和智能指针,编译器生成的默认析构函数就完全够用,且更安全高效。
性能影响:手写循环 delete p 和让 std::vector 自动调用每个 std::unique_ptr 的析构函数,生成的汇编几乎一样;但前者多了人为出错空间(比如漏删、重复删、顺序错)。
- 正确写法:
std::vector<:unique_ptr>> items;</:unique_ptr>—— 插入用items.push_back(std::make_unique<myclass>(...))</myclass>,析构自动发生 - 错误写法:
std::vector<myclass> raw_ptrs;</myclass>+ 手动循环delete,不仅容易内存泄漏,还会在异常中途退出时崩 - 如果需要共享所有权,用
std::shared_ptr,但注意循环引用风险;避免混用裸指针和智能指针指向同一块内存
真正难的不是写析构函数,而是判断“这个资源到底该由谁负责释放”。RAII 的规范本质是契约:谁构造,谁销毁;谁持有,谁负责。一旦出现“我传给你一个指针,你记得 delete”的口头约定,就已经脱离 RAII 了。










