析构函数

析构函数

C++

编译器支持

自由(freestanding)与宿主(hosted)

语言

标准库

标准库头文件

具名要求

特性测试宏 (C++20)

语言支持库

概念库 (C++20)

诊断库

内存管理库

元编程库 (C++11)

通用工具库

容器库

迭代器库

范围库 (C++20)

算法库

字符串库

文本处理库

数值库

日期和时间库

输入/输出库

文件系统库 (C++17)

并发支持库 (C++11)

执行控制库 (C++26)

技术规范

符号索引

外部库

[编辑] C++ 语言

通用主题

预处理器

注释

关键词

转义序列

流程控制

条件执行语句

if

switch

迭代语句(循环)

for

基于范围的 for (C++11)

while

do-while

跳转语句

continue - break

goto - return

函数

函数声明

Lambda 函数表达式

inline 说明符

动态异常规范 (直到 C++17*)

noexcept 说明符 (C++11)

异常

throw 表达式

try 块

catch 处理程序

命名空间

命名空间声明

命名空间别名

类型

基本类型

枚举类型

函数类型

类/结构体类型

联合类型

说明符

const/volatile

decltype (C++11)

auto (C++11)

constexpr (C++11)

consteval (C++20)

constinit (C++20)

存储期说明符

初始化

默认初始化

值初始化

零初始化

复制初始化

直接初始化

聚合初始化

列表初始化 (C++11)

常量初始化

引用初始化

表达式

值类别

求值顺序

运算符

运算符优先级

替代表示

字面量

布尔 - 整数 - 浮点

字符 - 字符串 - nullptr (C++11)

用户定义 (C++11)

工具

属性 (C++11)

类型

typedef 声明

类型别名声明 (C++11)

类型转换

隐式转换

static_cast

const_cast

显式转换

dynamic_cast

reinterpret_cast

内存分配

new 表达式

delete 表达式

类声明

构造函数

this 指针

访问说明符

friend 说明符

类特有的函数属性

虚函数

override 说明符 (C++11)

final 说明符 (C++11)

explicit (C++11)

static

特殊成员函数

默认构造函数

复制构造函数

移动构造函数 (C++11)

复制赋值

移动赋值 (C++11)

析构函数

模板

类模板

函数模板

模板特化

参数包 (C++11)

杂项

内联汇编

C++ 历史

[编辑] 类

通用

概览

class/struct 类型

union 类型

注入类名

类属性说明符 (C++26)

成员

数据成员

静态成员

this 指针

嵌套类

成员模板

位域

using-声明

成员函数

成员访问说明符

构造函数和成员初始化列表

默认成员初始化器 (C++11)

friend 说明符

explicit 说明符

转换构造函数

特殊成员函数

默认构造函数

复制构造函数

移动构造函数 (C++11)

复制赋值运算符

移动赋值运算符 (C++11)

析构函数

继承

基类和派生类

空基类优化 (EBO)

虚成员函数

纯虚函数和抽象类

override 说明符 (C++11)

final 说明符 (C++11)

[编辑]

析构函数是一种特殊的成员函数,它在对象的生存期结束时被调用。析构函数的目的是释放对象在其生存期内可能获取的资源。

析构函数不能是协程。

(C++20 起)

目录

1 语法

2 解释

3 准析构函数

4 可能被调用的析构函数

5 隐式声明的析构函数

6 隐式定义的析构函数

7 被删除的析构函数

8 平凡析构函数

9 销毁顺序

10 虚析构函数

11 纯虚析构函数

12 异常

13 注意

14 示例

15 缺陷报告

16 参阅

[编辑] 语法

析构函数(C++20 前)准析构函数(C++20 起)使用以下形式的成员函数声明符声明

带波浪号的类名 ( 形参列表 (可选) ) 异常说明 (可选) 属性 (可选)

带波浪号的类名

-

一个标识符表达式,后面可以跟随一个属性列表,并且(C++11 起)可以被一对括号括起来

形参列表

-

形参列表 (必须为空或 void)

异常规范

-

动态异常规范

(C++11 前)

或者动态异常规范或者noexcept 规范

(C++11 起)(C++17 前)

noexcept 规范的一部分

(C++17 起)

属性

-

(C++11 起) 属性列表

一个准(C++20 起)析构函数声明的声明说明符中允许的说明符只有constexpr、(C++11 起) friend、inline 和 virtual (特别地,不允许有返回类型)。

带波浪号的类名的标识符表达式必须具有以下形式之一

在属于类或类模板的成员说明,但不是友元声明的成员声明中

对于类,标识符表达式为 ~ 后跟直接外围类的注入类名。对于类模板,标识符表达式为 ~ 后跟指名当前实例化的类名(C++20 前)直接外围类模板的注入类名(C++20 起)。

否则,标识符表达式是一个限定标识符,其末端的非限定标识符为 ~ 后跟由该限定标识符的非末端部分所指名的类的注入类名。

[编辑] 解释

每当对象的生存期结束时,就会隐式调用析构函数,这包括:

对于具有静态存储期的对象,在程序终止时

对于具有线程局部存储期的对象,在线程退出时

(C++11 起)

对于具有自动存储期的对象以及其生命周期因绑定到引用而被延长的临时对象,在作用域结束时 对于具有动态存储期的对象,在使用 delete 表达式时对于无名临时对象,在完整表达式结束时 当异常逃离其块且未被捕获时,对于具有自动存储期的对象,在栈回溯期间

析构函数也可以被显式调用。

准析构函数

一个类可以有一个或多个准析构函数,其中一个被选为该类的析构函数。

为了确定哪个准析构函数是析构函数,在类定义结束时,会对类中声明的具有空参数列表的准析构函数执行重载决议。如果重载决议失败,则程序非良构。析构函数的选择不会ODR 式使用所选的析构函数,且所选的析构函数可以是被删除的。

所有准析构函数都是特殊成员函数。如果类 T 没有提供用户声明的准析构函数,编译器将总是隐式声明一个,并且这个隐式声明的准析构函数也就是 T 的析构函数。

运行此代码

#include

#include

template

struct A

{

~A() requires std::is_integral_v { std::puts("~A, T is integral"); }

~A() requires std::is_pointer_v { std::puts("~A, T is a pointer"); }

~A() { std::puts("~A, T is anything else"); }

};

int main()

{

A a;

A b;

A 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 的异常对象。

如果一个可能被调用的析构函数是被删除的或(C++11 起)从调用上下文中不可访问的,则程序非良构。

[编辑] 隐式声明的析构函数

如果一个类类型没有提供用户声明的准(C++20 起)析构函数,编译器将总是声明一个析构函数作为其类的 inline public 成员。

与任何隐式声明的特殊成员函数一样,隐式声明的析构函数的异常说明是非抛出的,除非任何可能被构造的基类或成员的析构函数是可能抛出的(C++17 起)隐式定义会直接调用一个具有不同异常说明的函数(C++17 前)。实际上,隐式析构函数是 noexcept 的,除非该类被一个基类或成员“毒化”,而该基类或成员的析构函数是 noexcept(false)。

[编辑] 隐式定义的析构函数

如果一个隐式声明的析构函数没有被删除,那么当它被 ODR 式使用时,编译器会隐式定义它(即,生成并编译一个函数体)。这个隐式定义的析构函数有一个空的函数体。

如果这满足了 constexpr 析构函数(C++23 前) constexpr 函数(C++23 起)的要求,那么生成的析构函数是 constexpr 的。

(C++20 起)

被删除的析构函数

类 T 的隐式声明或显式默认的析构函数被定义为已删除的,如果满足以下任何一个条件:

T 有一个类类型为 M 的可能被构造的子对象(或其多维数组),使得 M 的析构函数

被删除或从 T 的析构函数中不可访问,或者在该子对象是变体成员的情况下,是非平凡的。

(直到 C++26)

T 不是联合体,并且有一个类类型为 M 的非变体可能被构造的子对象(或其多维数组),使得 M 的析构函数被删除或从 T 的析构函数中不可访问。 T 是一个联合体,并且满足以下任何一个条件:

选择一个构造函数来默认初始化类型为 T 的对象的重载决议失败,或者选择的构造函数被删除或非平凡。 T 有一个类类型为 M 的变体成员 V(或其多维数组),其中 V 有一个默认初始化器且 M 的析构函数非平凡。

(C++26 起)

析构函数是虚函数,并且对释放函数的查找结果为

一个歧义,或一个被删除的或从析构函数中不可访问的函数。

如果一个显式默认的 T 的准析构函数不是 T 的析构函数,则它被定义为已删除。

(C++20 起)

(C++11 起)

[编辑] 平凡析构函数

类 T 的析构函数是平凡的,如果满足以下所有条件:

析构函数是隐式声明的(C++11 前)非用户提供的(C++11 起)。析构函数不是虚函数。所有直接基类都有平凡析构函数。

每个类类型(或类类型数组)的非静态数据成员都有一个平凡析构函数。

(直到 C++26)

T 是一个联合体,或者每个类类型(或类类型数组)的非变体非静态数据成员都有一个平凡析构函数。

(C++26 起)

平凡析构函数是一个不执行任何操作的析构函数。具有平凡析构函数的对象不需要 delete 表达式,可以通过简单地释放其存储来处理。所有与 C 语言兼容的数据类型(POD 类型)都是可平凡销毁的。

[编辑] 销毁顺序

对于用户定义的或隐式定义的析构函数,在执行完析构函数体并销毁了函数体内分配的任何自动对象之后,编译器会以声明的相反顺序调用类的所有非静态非变体数据成员的析构函数,然后以构造的相反顺序调用所有直接非虚基类的析构函数(这些析构函数又会调用它们的成员和基类的析构函数等),然后,如果这个对象是最终派生类,它会调用所有虚基类的析构函数。

即使直接调用析构函数(例如 obj.~Foo();),~Foo() 中的 return 语句也不会立即将控制权返回给调用者:它会先调用所有那些成员和基类的析构函数。

[编辑] 虚析构函数

通过指向基类的指针删除对象会引发未定义行为,除非基类中的析构函数是 virtual 的

class Base

{

public:

virtual ~Base() {}

};

class Derived : public Base {};

Base* b = new Derived;

delete b; // safe

一个常见的指导原则是,基类的析构函数必须是公有且虚的,或者是受保护且非虚的。

[编辑] 纯虚析构函数

一个准(C++20 起)析构函数可以被声明为纯虚的,例如在一个需要被设为抽象但没有其他合适的函数可以声明为纯虚的基类中。纯虚析构函数必须有定义,因为当派生类被销毁时,所有基类的析构函数总是会被调用。

class AbstractBase

{

public:

virtual ~AbstractBase() = 0;

};

AbstractBase::~AbstractBase() {}

class Derived : public AbstractBase {};

// AbstractBase obj; // compiler error

Derived obj; // OK

[编辑] 异常

与任何其他函数一样,析构函数可以通过抛出异常来终止(这通常要求它被显式声明为 noexcept(false))(C++11 起),但是,如果这个析构函数恰好在栈回溯期间被调用,那么会调用 std::terminate 代替。

虽然 std::uncaught_exceptions 有时可用于检测正在进行的栈回溯,但通常认为允许任何析构函数通过抛出异常来终止是一种不好的做法。尽管如此,一些库还是使用了这个功能,例如 SOCI 和 Galera 3,它们依赖于无名临时对象的析构函数在构造该临时对象的完整表达式结束时抛出异常的能力。

库基础 TS v3 中的 std::experimental::scope_success 可能有一个可能抛出的析构函数,它在作用域正常退出且退出函数抛出异常时抛出异常。

[编辑] 注意

对普通对象(如局部变量)直接调用析构函数,当在作用域结束时再次调用析构函数时,会引发未定义行为。

在泛型上下文中,析构函数调用语法可以用于非类类型的对象;这被称为伪析构函数调用:见成员访问运算符。

功能测试宏

标准

特性

__cpp_trivial_union

202502L

(C++26)

放宽联合体的特殊成员函数的平凡性要求

[编辑] 示例

运行此代码

#include

struct A

{

int i;

A(int num) : i(num)

{

std::cout << "ctor a" << i << '\n';

}

(~A)() // but usually ~A()

{

std::cout << "dtor a" << i << '\n';

}

};

A a0(0);

int main()

{

A a1(1);

A* p;

{ // nested scope

A a2(2);

p = new A(3);

} // a2 out of scope

delete p; // calls the destructor of a3

}

输出

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

已禁止

[编辑] 参阅

复制省略

new

delete

相关发现

阴阳师惠比寿值得培养吗
365是正规平台吗

阴阳师惠比寿值得培养吗

🌼 09-24 🌻 2619
28岁男子熬夜看世界杯险送命!这几天医院心梗病人破纪录了……
《王者荣耀》荣耀战令升80时间表 荣耀战令80级需要多少经验
充到cf手游v5需要多少钱?充值的最佳策略是什么?
365是正规平台吗

充到cf手游v5需要多少钱?充值的最佳策略是什么?

🌼 07-28 🌻 1796