析构函数在对象生命周期结束时由系统自动触发,而非手动调用;栈对象在离开作用域时逆序析构,static局部变量在main返回后析构,临时对象在完整表达式结束时析构,动态对象需显式delete才析构,异常时栈展开确保已构造对象析构,移动语义不改变析构时机。

析构函数在对象生命周期结束时自动执行,不是你调用它,而是系统在对象“消失”的那一刻触发它。
对象离开作用域时析构函数被调用
这是最常见场景:栈上对象(局部变量)在所在代码块结束大括号 } 时立刻执行析构。比如函数返回、if 块结束、循环迭代结束——只要控制流走出作用域边界,编译器就插入析构调用。
注意点:
- 不是“函数返回前”,而是“离开作用域的那一刻”,哪怕函数里有
return语句,该作用域内所有局部对象仍会按构造逆序析构 - 如果对象是
static局部变量,析构发生在main()返回后、程序退出前,顺序与构造相反 - 临时对象(如函数返回值、隐式转换产生的对象)通常在完整表达式结束时析构,但 C++17 后可能因强制拷贝省略(copy elision)而跳过构造/析构——这点容易误判生命周期
动态分配对象必须手动 delete 才会触发析构
用 new 创建的对象不会自动析构;只有显式调用 delete(或 delete[])时,才会先执行析构函数,再释放内存。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:
- 忘了
delete→ 析构函数永不执行,资源泄漏(文件句柄、内存、锁等) - 重复
delete→ 析构函数执行两次,未定义行为(UB),常伴随崩溃或数据错乱 - 用
free()释放new出来的对象 → 析构函数完全不执行,只释放内存
建议:优先用智能指针(std::unique_ptr / std::shared_ptr),它们在离开作用域时自动调用 delete,从而保证析构。
异常发生时析构函数仍会执行(栈展开)
当抛出异常且当前作用域有未析构的局部对象时,C++ 会自动进行栈展开(stack unwinding):逐层跳出作用域,并对每个已构造完成的局部对象调用析构函数。
关键限制:
- 仅对“已完全构造”的对象析构;若构造函数中途抛异常,该对象的析构函数不会执行(因为还没构造完)
- 析构函数自己再抛异常(且未被捕获)→ 程序直接调用
std::terminate()终止,无法恢复 - 因此,析构函数应声明为
noexcept(C++11 起默认如此),避免意外传播异常
移动语义下析构函数的执行时机没变,但对象状态可能已失效
移动操作(如 std::move)本身不触发析构;被移动的对象(源对象)仍存在,只是处于有效但未指定状态,它的析构函数仍会在原定时间点执行。
容易踩的坑:
- 在移动构造/赋值函数中,若未将源对象的资源指针置为
nullptr,其析构函数可能再次释放已被转移走的资源(double free) - 移动后继续访问源对象成员(尤其裸指针、文件描述符等)→ 行为未定义,不是析构没执行,而是对象已“空壳”
- 移动语义不影响析构时机,只影响对象内容;别误以为“被移动了就不用析构了”
析构函数是否执行,只取决于对象是否“寿终正寝”,和它中间经历过什么(拷贝、移动、异常)无关;真正难的是确保它执行时,所依赖的资源还有效、没被提前释放、也没被重复释放。









