析构函数在对象生命周期结束时自动调用,用于释放成员持有的资源、断开连接等清理工作。函数名为 ~类名,无参数、无返回值、不可重载。本节说明析构的触发时机、与构造的对称关系、成员析构顺序,以及继承与虚析构的必要性。
基本析构与 RAII
#include <iostream>
class FileGuard {
public:
FileGuard() { std::cout << "open\n"; }
~FileGuard() { std::cout << "close\n"; }
};
int main() {
FileGuard g;
std::cout << "working\n";
return 0;
}输出:
open
working
close离开 main 时先析构局部对象 g,再结束程序。析构与构造体现 RAII,与智能指针、异常栈展开一致。
何时调用析构
- 自动存储期对象离开其作用域。
delete指针所指动态对象;delete[]数组元素按相反构造顺序析构。- 临时对象在完整表达式结束处析构(存在延长规则例外)。
- 程序结束时常量静态对象按相反顺序析构。
成员与容器的析构
类对象析构时,先执行用户定义的析构函数体,再按成员声明相反顺序析构各数据成员。成员为 std::string、std::vector 等时,其析构自动释放内部资源,通常无需在类析构中重复编写。
#include <vector>
#include <string>
class Bundle {
std::string name_;
std::vector<int> data_;
// 隐式析构即可释放 name_ 与 data_
};仅在类直接管理裸指针、文件句柄等须手写释放逻辑;更宜改用智能指针或封装类。
继承链中的析构
#include <iostream>
#include <memory>
class Base {
public:
virtual ~Base() { std::cout << "~Base\n"; }
};
class Derived : public Base {
public:
~Derived() override { std::cout << "~Derived\n"; }
};
int main() {
std::unique_ptr<Base> p = std::make_unique<Derived>();
return 0;
}典型输出:先 ~Derived,再 ~Base。销毁 Derived 完整对象时派生析构先于基类。
非虚析构的风险
若通过 Base* 指向 Derived 并 delete,而 ~Base 非虚,则只运行 ~Base,~Derived 不执行,派生资源泄漏。多态基类应声明 virtual ~Base() = default;,见多态。
不要手动调用普通对象析构
对自动存储期对象显式调用析构(p.~T();)后,对象存储仍占用,作用域结束时会再次析构,属于未定义行为。仅 placement new、显式生命周期管理等高级场景在标准允许下使用。
析构与异常
析构函数默认 noexcept(C++11 起对析构的特殊规则)。析构中抛出异常可能导致 std::terminate;资源释放应简单、不依赖可能失败的复杂逻辑。
三/五/零法则(概览)
| 法则 | 含义 |
|---|---|
| 三法则(经典) | 若自定义析构、拷贝构造或拷贝赋值之一,通常需考虑三者 |
| 五法则(C++11) | 加上移动构造与移动赋值 |
| 零法则 | 用 RAII 成员(容器、智能指针)则不必手写资源管理特殊成员 |
现代代码多遵循零法则,移动语义见移动语义。
本节说明了析构函数的作用与虚析构场景。下一章从枚举与 enum class开始,讨论具名常量集合。