析构函数在对象生命周期结束时由编译器自动调用,时机取决于存储期:栈对象离开作用域时逆序析构,堆对象需显式delete,static/thread_local对象在程序/线程退出时逆序析构但跨单元顺序未定义,异常时栈展开确保已构造对象析构,析构函数应声明为noexcept。

析构函数在对象生命周期结束时自动调用
析构函数不是手动触发的,它由编译器在对象“不再可用”的确切时刻插入调用。这个时机取决于对象的存储期(storage duration):static、thread_local、automatic 或 dynamic。
栈上对象的析构时机很明确:离开作用域时
对于局部变量(automatic 存储期),析构发生在其声明所在的作用域末尾(右大括号 } 处),且按构造的**逆序**执行。注意不是“函数返回时”才统一调用——如果作用域提前结束(如 return、throw、goto),析构仍会立即发生。
常见错误现象:
- 在
if分支中定义对象,误以为它“活到函数末尾” - 用
std::vector存了局部对象指针,却忘了对象本身早已析构,留下悬空指针
示例:
立即学习“C++免费学习笔记(深入)”;
void f() {
{
std::string s1("hello");
std::string s2("world");
// s2 先析构,s1 后析构
} // ← 这里 s2 和 s1 都已析构
// 此处访问 s1 或 s2 是未定义行为
}
堆上对象必须显式 delete,否则不析构
用 new 创建的对象拥有 dynamic 存储期,析构**仅当且仅当**执行对应的 delete(或 delete[])时发生。忘记 delete → 内存泄漏 + 析构函数永不执行;重复 delete → 未定义行为(常见崩溃)。
使用建议:
- 优先用智能指针(
std::unique_ptr、std::shared_ptr),它们在自身析构时自动调用delete - 避免裸指针管理资源;若必须用,确保
new/delete成对出现在同一作用域或 RAII 封装内 -
std::vector存的是对象副本,不是指针;存指针时析构不等于释放所指对象
静态和线程局部对象的析构顺序不可靠
static(包括全局、命名空间作用域、函数内 static)和 thread_local 对象,在程序/线程退出时析构,但顺序与构造顺序相反,且跨编译单元的顺序是未定义的。
这意味着:
- 一个
static对象的析构函数里访问另一个static对象,可能后者已析构完毕 → 访问已销毁对象,UB -
atexit()注册的函数与静态析构的相对顺序也不保证 - 推荐用“局部静态变量 + 函数返回引用”方式延迟初始化(Meyers Singleton),规避跨单元依赖
典型坑:
// file1.cpp
static std::ofstream log_file("app.log");
// file2.cpp
static Logger logger; // 构造时可能想往 log_file 写日志
// → 无法保证 log_file 在 logger 之前构造,更无法保证析构时不写已关闭的流
最易被忽略的一点:异常中途抛出时,栈展开(stack unwinding)会严格按作用域嵌套关系调用所有已构造完成的局部对象析构函数——但前提是这些析构函数不能抛异常(否则直接调用 std::terminate)。所以,析构函数应尽量设为 noexcept。









