Destructors
析构函数是一种特殊的 成员函数 ,当 对象的生命周期 结束时被调用。析构函数的目的是释放对象在其生命周期内可能获取的资源。
|
析构函数不能是 协程 。 |
(since C++20) |
目录 |
语法
析构函数 (C++20 前) 预期析构函数 (C++20 起) 使用以下形式的成员 函数声明符 进行声明:
类名带波浪号
(
参数列表
(可选)
)
异常说明
(可选)
属性
(可选)
|
|||||||||
| class-name-with-tilde | - | 一个 标识符表达式 , 可能后跟一组 属性 ,且 (自 C++11 起) 可能被一对圆括号包围 | ||||||
| parameter-list | - | 形参列表 (必须为空或为 void ) | ||||||
| except | - |
|
||||||
| attr | - | (自 C++11 起) 一组 属性 |
在
预期
(C++20 起)
析构函数声明的
声明说明符
中,唯一允许的说明符是
constexpr
、
(C++11 起)
friend
、
inline
和
virtual
(特别地,不允许出现返回类型)。
标识符表达式 class-name-with-tilde 必须具有以下形式之一:
- 否则,该标识符表达式是一个限定标识符,其终端非限定标识符由 ~ 后接由该限定标识符非终端部分所指定类的注入类名组成。
说明
析构函数在对象 生命周期 结束时被隐式调用,包括
|
(since C++11) |
- 作用域结束,适用于具有自动存储期的对象以及通过绑定到引用而延长生命期的临时对象
- delete 表达式 ,适用于具有动态存储期的对象
- 完整 表达式 结束,适用于无名临时对象
- 栈展开 ,当异常逃逸其代码块且未被捕获时,适用于具有自动存储期的对象
析构函数也可以被显式调用。
候选析构函数一个类可以拥有一个或多个候选析构函数,其中将被选定一个作为该类的析构函数。 为了确定哪个候选析构函数是最终的析构函数,在类定义结束时,将在类中声明的候选析构函数之间执行 重载决议 ,参数列表为空。如果重载决议失败,则程序非良构。析构函数的选择不会 ODR 使用 被选中的析构函数,且被选中的析构函数可以是已删除的。
所有候选析构函数都是特殊成员函数。如果类
运行此代码
#include <cstdio> #include <type_traits> template<typename T> struct A { ~A() requires std::is_integral_v<T> { std::puts("~A, T is integral"); } ~A() requires std::is_pointer_v<T> { std::puts("~A, T is a pointer"); } ~A() { std::puts("~A, T is anything else"); } }; int main() { A<int> a; A<int*> b; A<float> c; } 输出: ~A, T is anything else ~A, T is a pointer ~A, T is integral |
(自 C++20 起) |
可能被调用的析构函数
类
T
的析构函数在以下情况下
可能被调用
:
- 它被显式或隐式调用。
-
new
表达式
创建类型为
T的对象数组。 -
return
语句
的结果对象类型为
T。 -
数组正在进行
聚合初始化
,且其元素类型为
T。 -
类对象正在进行聚合初始化,且其拥有类型为
T的成员(其中T不是 匿名联合 类型)。 -
在
非
委托
(C++11 起)
构造函数中,
潜在构造子对象
的类型为
T。 -
类型为
T的 异常对象 被构造。
如果可能被调用的析构函数是 被删除或 (since C++11) 在调用上下文中不可访问,则程序非良构。
隐式声明的析构函数
如果类类型没有提供用户声明的 预期 (since C++20) 析构函数,编译器将始终声明一个析构函数作为其类的 inline public 成员。
与任何隐式声明的特殊成员函数一样,隐式声明的析构函数的异常规范是 不抛出异常的 ,除非 任何潜在构造的基类或成员的析构函数是 潜在抛出的 (C++17 起) 隐式定义会直接调用具有不同异常规范的函数 (C++17 前) 。实际上,隐式析构函数都是 noexcept 的,除非类被具有 noexcept ( false ) 析构函数的基类或成员"污染"。
隐式定义的析构函数
如果隐式声明的析构函数未被删除,则当编译器 ODR使用 时会隐式定义(即生成并编译函数体)。该隐式定义的析构函数具有空函数体。
|
若此满足 constexpr 析构函数 (C++23 前) constexpr 函数 (C++23 起) 的要求,则生成的析构函数为 constexpr 。 |
(C++20 起) |
被删除的析构函数
当满足以下任意条件时,类
|
(C++11 起) |
平凡析构函数
当满足以下所有条件时,类
T
的析构函数是平凡的:
- 析构函数是 隐式声明的 (C++11 前) 非 用户提供 的 (C++11 起) 。
- 析构函数不是虚函数。
- 所有直接基类都具有平凡析构函数。
|
(直至 C++26) |
|
(自 C++26 起) |
平凡析构函数是不执行任何操作的析构函数。具有平凡析构函数的对象不需要 delete 表达式,仅通过释放其存储空间即可完成销毁。所有与C语言兼容的数据类型(POD类型)都具有平凡可析构性。
析构序列
对于用户定义或隐式定义的析构函数,在执行完析构函数体并销毁函数体内分配的所有自动对象后,编译器会按声明顺序的逆序调用该类的所有非静态非变体数据成员的析构函数,随后按 构造顺序的逆序 调用所有直接非虚基类的析构函数(这些基类析构函数会继续调用其成员和基类的析构函数,依此类推),最后若该对象属于 最终派生类 ,则会调用所有虚基类的析构函数。
即使直接调用析构函数(例如 obj.~Foo ( ) ; ), return 语句在 ~Foo ( ) 中也不会立即将控制权返回给调用者:它会先调用所有成员和基类的析构函数。
虚析构函数
通过基类指针删除对象会引发未定义行为,除非基类中的析构函数是 virtual :
class Base { public: virtual ~Base() {} }; class Derived : public Base {}; Base* b = new Derived; delete b; // 安全
一个常见的指导原则是,基类的析构函数必须 要么是公开且虚函数,要么是受保护且非虚函数 。
纯虚析构函数
一个 预期 (C++20 起) 析构函数可以声明为 纯虚函数 ,例如在需要设为抽象类但不存在其他适合声明为纯虚函数的基类中。纯虚析构函数必须具有定义,因为当派生类被销毁时,所有基类析构函数总是会被调用:
class AbstractBase { public: virtual ~AbstractBase() = 0; }; AbstractBase::~AbstractBase() {} class Derived : public AbstractBase {}; // AbstractBase obj; // 编译器错误 Derived obj; // 正确
异常
与其他函数一样,析构函数可能通过抛出 异常 来终止 (这通常需要显式声明为 noexcept ( false ) ) (C++11 起) ,但若该析构函数恰好在 栈展开 过程中被调用,则会改为调用 std::terminate 。
尽管 std::uncaught_exceptions 有时可用于检测正在进行的栈展开,但允许任何析构函数通过抛出异常来终止通常被视为不良实践。不过某些库仍会使用此功能,例如 SOCI 和 Galera 3 ,这些库依赖于无名临时对象的析构函数在构造该临时对象的完整表达式结束时抛出异常的能力。
std::experimental::scope_success 在 Library fundamental TS v3 中可能具有 潜在抛出异常的析构函数 ,当作用域正常退出且退出函数抛出异常时,该析构函数会抛出异常。
注释
对普通对象(如局部变量)直接调用析构函数,会在作用域结束时再次调用析构函数,这将引发未定义行为。
在泛型语境中,析构函数调用语法可用于非类类型的对象;这被称为伪析构函数调用:参见 成员访问运算符 。
| 特性测试宏 | 值 | 标准 | 特性 |
|---|---|---|---|
__cpp_trivial_union
|
202502L
|
(C++26) | 放宽联合体特殊成员函数的平凡性要求 |
示例
输出:
ctor a0 ctor a1 ctor a2 ctor a3 dtor a2 dtor a3 dtor a1 dtor a0
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 193 | C++98 | 未指定析构函数中的自动对象是在类的基类与成员子对象销毁之前还是之后被销毁 | 应在销毁这些子对象之前销毁自动对象 |
| CWG 344 | C++98 | 析构函数的声明符语法存在缺陷(与 CWG 194 和 CWG 263 问题相同) | 将语法改为专用函数声明符语法 |
| CWG 1241 | C++98 | 静态成员可能在析构函数执行后立即被销毁 | 仅销毁非静态成员 |
| CWG 1353 | C++98 | 隐式声明析构函数未定义的条件未考虑多维数组类型 | 需考虑这些类型 |
| CWG 1435 | C++98 | 析构函数声明符语法中“类名”的含义不明确 | 将语法改为专用函数声明符语法 |
| CWG 2180 | C++98 | 非最终派生类的析构函数会调用其虚直接基类的析构函数 | 不会调用这些析构函数 |
| CWG 2807 | C++20 | 声明说明符可能包含 consteval | 已禁止该用法 |