析构函数在对象生命周期结束时自动调用:栈对象在作用域退出时、堆对象在delete后、静态对象在main返回后按构造逆序析构;异常栈展开和new/delete配对错误是常见隐患。

析构函数在对象生命周期结束时自动调用
析构函数不是手动触发的,而是由 C++ 运行期在对象“生命终结”的确切时刻自动调用。这个时刻取决于对象的存储期(storage duration):栈对象在作用域退出时调用,堆对象在 delete 或 delete[] 执行后调用,静态对象在 main() 返回后、程序终止前调用。
栈对象析构时机:作用域退出即触发
这是最常见也最容易误判的场景。析构发生在控制流离开定义该对象的作用域大括号 {} 的右括号之后,**不是**在 return 语句执行时、也不是在函数末尾“看起来结束”的地方。
- 如果函数提前
return,仍会调用已构造完成的栈对象的析构函数 - 异常抛出导致栈展开(stack unwinding)时,所有已构造、尚未析构的栈对象也会被依次析构
- 临时对象(如函数返回值、表达式中间结果)通常在完整表达式求值结束后立即析构,但 C++17 引入了强制拷贝省略(mandatory copy elision),可能让某些临时对象“消失”,从而跳过其析构
堆对象析构:只响应显式 delete,不响应 free 或未配对 delete
用 new 分配的对象,必须用 delete 销毁;用 new[] 分配的数组,必须用 delete[] 销毁。混用或遗漏会导致未定义行为(UB),典型表现包括:
-
delete一个用new[]分配的指针 → 可能只调用第一个元素的析构,其余不调用,内存未正确释放 - 用
free()释放new出来的内存 → 析构函数完全不调用,资源泄漏 - 多次
delete同一指针 → 第二次起是 UB,析构可能重复执行或崩溃
静态/全局对象析构:顺序与生命周期交叉风险
静态存储期对象(包括全局变量、命名空间作用域变量、static 局部变量)的析构发生在 main() 返回之后,按构造的**逆序**进行。这带来两个关键约束:
立即学习“C++免费学习笔记(深入)”;
- 不能在某个静态对象的析构函数中访问另一个**尚未析构**(但可能已析构完)的静态对象 —— 顺序不可靠,容易访问到已销毁对象的成员
-
atexit()注册的函数在静态析构之前运行,因此也不应依赖任何静态对象的状态 -
static局部变量首次进入作用域时构造,其析构时间点晚于全局变量,但早于线程结束(C++11 起保证线程安全初始化)
new/delete 配对错误导致的静默失效 —— 这些地方不会报编译错误,但运行时行为难以预测。







