编译器提供的合成版本: 默认构造函数, 析构函数, 拷贝构造函数, 拷贝赋值运算符
2023年12月30日 2024年1月1日
显式要求编译器提供指定的合成版本
-
要求合成版本为
default
=default
编译器一定会给出指定操作的合成版本, 但不一定是
default
; 可能是delete
-
要求合成版本为
delete
=delete
编译器一定可以满足
编译器尝试提供 default
的合成版本
-
满足以下条件时, 编译器尝试提供
default
的合成版本条件 默认构造函数 未定义任何构造函数 析构函数 未定义析构函数 拷贝构造函数 未定义构造函数 拷贝赋值运算符 未定义拷贝赋值运算符 编译器一定会提供合成版本, 或为
default
, 或为delete
-
满足以下条件时, 编译器能够提供
default
的合成版本注意: 必要非充分
非 static
数据成员类型默认构造函数 类类型数据成员的默认构造函数非删除 delete
且可访问(非私有private
), 或者拥有类内初始值;引用类型数据成员拥有类内初始值; 具有顶层const的类类型数据成员拥有非合成的默认构造函数, 或者拥有类内初始值; 类类型数据成员的析构函数非删除 delete
且可访问(非私有private
)析构函数 类类型数据成员的析构函数非删除 delete
且可访问(非私有private
)拷贝构造函数 类类型数据成员的拷贝构造函数非删除 delete
且可访问(非私有private
);类类型数据成员的析构函数非删除 delete
且可访问(非私有private
)拷贝赋值运算符 类类型数据成员的拷贝赋值运算符非删除 delete
且可访问(非私有private
);数据成员均不具有顶层const; 引用类型数据成员不具有底层const(指针类型数据成员可以具有底层const) 基于以下核心点:
-
不能创建一个无法销毁的临时对象: 要求非
static
类类型数据成员的析构函数非删除delete
且可访问(非私有private
)
默认构造函数/拷贝构造函数 + 析构函数 -
必须初始化非
static
引用类型数据成员: 拥有类内初始值
默认构造函数/拷贝构造函数 -
必须初始化非
static
的具有顶层const的类类型数据成员: 类类型拥有非合成的默认构造函数, 或者数据成员拥有类内初始值
默认构造函数/拷贝构造函数 -
不能对拥有顶层const的对象执行赋值操作: 如果拥有非
static
的具有顶层const的数据成员, 拷贝赋值运算符的合成版本为删除delete
否则, 编译器提供的合成版本为
delete
本质上, 当不可能拷贝, 赋值或销毁类的数据成员时, 类的合成拷贝控制操作就被定义为删除
delete
-
隐式 =default
满足编译器尝试提供 default
的合成版本的条件
合成版本可能为 default
, 可能为 delete
隐式 delete
满足编译器尝试提供 default
的合成版本的条件, 但存在非 static
数据成员不满足条件的情形
显式 delete
- 显式要求编译器提供
=default
的合成版本, 但存在非static
数据成员不满足条件的情形 - 显式要求编译器提供
=delete
的合成版本
示例: 合成版本为 default
的默认构造函数, 对具有顶层const的类类型数据成员的默认构造函数的要求
-
要求默认构造函数非合成
1#include <iostream> 2 3using namespace std; 4 5class test 6{ 7public: 8 test() = default; 9 10private: 11 int a; 12}; 13 14class Foo 15{ 16public: 17 void PrintFoo() { cout << a; } 18 19private: 20 int a; 21 const test t; 22}; 23 24int main() 25{ 26 Foo f; 27 return 0; 28}
报错
error: call to implicitly-deleted default constructor of 'Foo' // Foo的默认构造函数隐式删除 Foo f; ^ note: default constructor of 'Foo' is implicitly deleted because field 't' of const-qualified type 'const test' would not be initialized const test t; ^ // 因为具有顶层const的对象t无法被初始化: 其默认构造函数未显式定义 1 error generated.
-
类类型拥有非合成的默认构造函数与必须显式初始化具有顶层const的内置类型对象的意图一致
基于一个约定: 在构造函数中妥善初始化所有非
static
数据成员, 由程序员保证在一定程度上保证了内置类型对象有初值
-
Foo的数据成员t具有顶层const, 但对其执行默认初始化后, t的内置类型数据成员a拥有未定义初值
-
因为显式定义了test类型的默认构造函数, 编译器为Foo合成了
default
的默认构造函数
编译和运行都不会报错
1#include <iostream> 2 3using namespace std; 4 5class test 6{ 7public: 8 test() {} // 虽然显式定义了默认构造函数, 但并未履行构造函数应尽的职责 9 10private: 11 int a; 12}; 13 14class Foo 15{ 16public: 17 void PrintFoo() { cout << a; } 18 19private: 20 int a; 21 const test t; 22}; 23 24int main() 25{ 26 Foo f; 27 return 0; 28}
-
如果类拥有非 static
引用类型数据成员, 书上建议将拷贝赋值运算符定义为删除
P451
引用的绑定不可更改
按实际需求来: 是要对绑定的对象执行赋值操作, 还是想要修改绑定关系
显式指定合成版本
1class T 2{ 3public: 4 T() = default; 5 6 T(T &) = default; 7 T(const T &) = default; 8 9 ~T() = default; 10 11 T &operator=(T &) = default; 12 T &operator=(const T &) = default; 13};
1class T 2{ 3public: 4 T() = delete; 5 6 T(T &) = delete; 7 T(const T &) = delete; 8 9 ~T() = delete; 10 11 T &operator=(T &) = delete; 12 T &operator=(const T &) = delete; 13};
以上合成版本均为隐式内联 inline
=delete
必须出现在第一次声明时给出
显式要求编译器提供非内联的 =default
合成版本
1class T 2{ 3public: 4 T(); 5}; 6 7T::T() = default;
阻止拷贝
-
对于某些类来说, 拷贝构造函数和拷贝赋值运算符没有合理的意义
此种情况下, 定义类时必须采用某种机制阻止拷贝和赋值
如iostream类和unique_ptr
由于当我们未定义拷贝构造函数和拷贝赋值运算符时, 编译器会为它们生成合成版本, 我们需要将其定义为显式删除
=delete
: 对其进行声明, 但不能以任何方式使用它们 -
当我们希望引导函数匹配过程时, 有时会将函数定义为删除
=delete
我们只能对默认构造函数和拷贝控制成员使用=default
, 但可以对任何函数指定=delete
=delete
必须出现在第一次声明时给出
=delete
出现之前
通过将拷贝构造函数和拷贝赋值运算符定义为私有 private
来阻止拷贝
存在漏洞: 类的其他函数成员和友元仍能使用拷贝构造函数和拷贝赋值运算符
因此, 只声明拷贝构造函数和拷贝赋值运算符为私有 private
, 而不去定义它们
声明但不定义一个成员是合法的; 如果在代码中试图访问这些成员, 将导致链接时错误
删除 delete
的析构函数
如果某个类的析构函数为删除 delete
, 无法销毁该类型对象
对于这种类, 编译器不允许定义该类型的临时对象; 我们可以定义该类型的动态对象, 但仍旧不能通过析构函数销毁动态对象, 除非使用删除器
如果某个类拥有这种数据成员, 编译器同样不允许定义该类型的临时对象; 我们可以定义该类型的动态对象, 但仍旧不能通过析构销毁动态对象, 除非使用删除器
未验证删除器
即定义一种只能创建该类型动态对象的类; 未验证