析构函数在对象生命周期结束时自动调用,用于释放成员持有的资源、断开连接等清理工作。函数名为 ~类名,无参数、无返回值、不可重载。本节说明析构的触发时机、与构造的对称关系、成员析构顺序,以及继承与虚析构的必要性。

基本析构与 RAII

C++ 析构与 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开始,讨论具名常量集合。