六一的部落格


关关难过关关过,前路漫漫亦灿灿。




显式要求编译器提供指定的合成版本

  1. 要求合成版本为 default

    =default

    编译器一定会给出指定操作的合成版本, 但不一定是 default ; 可能是 delete

  2. 要求合成版本为 delete

    =delete

    编译器一定可以满足


编译器尝试提供 default 的合成版本

  1. 满足以下条件时, 编译器尝试提供 default 的合成版本

    条件
    默认构造函数 未定义任何构造函数
    析构函数 未定义析构函数
    拷贝构造函数 未定义构造函数
    拷贝赋值运算符 未定义拷贝赋值运算符

    编译器一定会提供合成版本, 或为 default , 或为 delete

  2. 满足以下条件时, 编译器能够提供 default 的合成版本

    注意: 必要非充分

    static 数据成员类型
    默认构造函数 类类型数据成员的默认构造函数非删除 delete 且可访问(非私有 private ), 或者拥有类内初始值;
    引用类型数据成员拥有类内初始值;
    具有顶层const的类类型数据成员拥有非合成的默认构造函数, 或者拥有类内初始值;
    类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )
    析构函数 类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )
    拷贝构造函数 类类型数据成员的拷贝构造函数非删除 delete 且可访问(非私有 private );
    类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )
    拷贝赋值运算符 类类型数据成员的拷贝赋值运算符非删除 delete 且可访问(非私有 private );
    数据成员均不具有顶层const;
    引用类型数据成员不具有底层const(指针类型数据成员可以具有底层const)

    基于以下核心点:

    1. 不能创建一个无法销毁的临时对象: 要求非 static 类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )

      默认构造函数/拷贝构造函数 + 析构函数

    2. 必须初始化非 static 引用类型数据成员: 拥有类内初始值

      默认构造函数/拷贝构造函数

    3. 必须初始化非 static 的具有顶层const的类类型数据成员: 类类型拥有非合成的默认构造函数, 或者数据成员拥有类内初始值

      默认构造函数/拷贝构造函数

    4. 不能对拥有顶层const的对象执行赋值操作: 如果拥有非 static 的具有顶层const的数据成员, 拷贝赋值运算符的合成版本为删除 delete

    否则, 编译器提供的合成版本为 delete

    本质上, 当不可能拷贝, 赋值或销毁类的数据成员时, 类的合成拷贝控制操作就被定义为删除 delete


隐式 =default

满足编译器尝试提供 default 的合成版本的条件

合成版本可能为 default , 可能为 delete


隐式 delete

满足编译器尝试提供 default 的合成版本的条件, 但存在非 static 数据成员不满足条件的情形


显式 delete

  1. 显式要求编译器提供 =default 的合成版本, 但存在非 static 数据成员不满足条件的情形
  2. 显式要求编译器提供 =delete 的合成版本

示例: 合成版本为 default 的默认构造函数, 对具有顶层const的类类型数据成员的默认构造函数的要求

  1. 要求默认构造函数非合成

     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.
  2. 类类型拥有非合成的默认构造函数与必须显式初始化具有顶层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;

阻止拷贝

  1. 对于某些类来说, 拷贝构造函数和拷贝赋值运算符没有合理的意义

    此种情况下, 定义类时必须采用某种机制阻止拷贝和赋值

    如iostream类和unique_ptr

    由于当我们未定义拷贝构造函数和拷贝赋值运算符时, 编译器会为它们生成合成版本, 我们需要将其定义为显式删除 =delete : 对其进行声明, 但不能以任何方式使用它们

  2. 当我们希望引导函数匹配过程时, 有时会将函数定义为删除 =delete

    我们只能对默认构造函数和拷贝控制成员使用 =default , 但可以对任何函数指定 =delete

=delete 必须出现在第一次声明时给出


=delete 出现之前

通过将拷贝构造函数和拷贝赋值运算符定义为私有 private 来阻止拷贝

存在漏洞: 类的其他函数成员和友元仍能使用拷贝构造函数和拷贝赋值运算符

因此, 只声明拷贝构造函数和拷贝赋值运算符为私有 private , 而不去定义它们

声明但不定义一个成员是合法的; 如果在代码中试图访问这些成员, 将导致链接时错误


删除 delete 的析构函数

如果某个类的析构函数为删除 delete , 无法销毁该类型对象

对于这种类, 编译器不允许定义该类型的临时对象; 我们可以定义该类型的动态对象, 但仍旧不能通过析构函数销毁动态对象, 除非使用删除器

如果某个类拥有这种数据成员, 编译器同样不允许定义该类型的临时对象; 我们可以定义该类型的动态对象, 但仍旧不能通过析构销毁动态对象, 除非使用删除器

未验证删除器

即定义一种只能创建该类型动态对象的类; 未验证


编译器提供的合成版本: 默认构造函数, 析构函数, 拷贝构造函数, 拷贝赋值运算符



显式要求编译器提供指定的合成版本

  1. 要求合成版本为 default

    =default

    编译器一定会给出指定操作的合成版本, 但不一定是 default ; 可能是 delete

  2. 要求合成版本为 delete

    =delete

    编译器一定可以满足


编译器尝试提供 default 的合成版本

  1. 满足以下条件时, 编译器尝试提供 default 的合成版本

    条件
    默认构造函数 未定义任何构造函数
    析构函数 未定义析构函数
    拷贝构造函数 未定义构造函数
    拷贝赋值运算符 未定义拷贝赋值运算符

    编译器一定会提供合成版本, 或为 default , 或为 delete

  2. 满足以下条件时, 编译器能够提供 default 的合成版本

    注意: 必要非充分

    static 数据成员类型
    默认构造函数 类类型数据成员的默认构造函数非删除 delete 且可访问(非私有 private ), 或者拥有类内初始值;
    引用类型数据成员拥有类内初始值;
    具有顶层const的类类型数据成员拥有非合成的默认构造函数, 或者拥有类内初始值;
    类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )
    析构函数 类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )
    拷贝构造函数 类类型数据成员的拷贝构造函数非删除 delete 且可访问(非私有 private );
    类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )
    拷贝赋值运算符 类类型数据成员的拷贝赋值运算符非删除 delete 且可访问(非私有 private );
    数据成员均不具有顶层const;
    引用类型数据成员不具有底层const(指针类型数据成员可以具有底层const)

    基于以下核心点:

    1. 不能创建一个无法销毁的临时对象: 要求非 static 类类型数据成员的析构函数非删除 delete 且可访问(非私有 private )

      默认构造函数/拷贝构造函数 + 析构函数

    2. 必须初始化非 static 引用类型数据成员: 拥有类内初始值

      默认构造函数/拷贝构造函数

    3. 必须初始化非 static 的具有顶层const的类类型数据成员: 类类型拥有非合成的默认构造函数, 或者数据成员拥有类内初始值

      默认构造函数/拷贝构造函数

    4. 不能对拥有顶层const的对象执行赋值操作: 如果拥有非 static 的具有顶层const的数据成员, 拷贝赋值运算符的合成版本为删除 delete

    否则, 编译器提供的合成版本为 delete

    本质上, 当不可能拷贝, 赋值或销毁类的数据成员时, 类的合成拷贝控制操作就被定义为删除 delete


隐式 =default

满足编译器尝试提供 default 的合成版本的条件

合成版本可能为 default , 可能为 delete


隐式 delete

满足编译器尝试提供 default 的合成版本的条件, 但存在非 static 数据成员不满足条件的情形


显式 delete

  1. 显式要求编译器提供 =default 的合成版本, 但存在非 static 数据成员不满足条件的情形
  2. 显式要求编译器提供 =delete 的合成版本

示例: 合成版本为 default 的默认构造函数, 对具有顶层const的类类型数据成员的默认构造函数的要求

  1. 要求默认构造函数非合成

     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.
  2. 类类型拥有非合成的默认构造函数与必须显式初始化具有顶层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;

阻止拷贝

  1. 对于某些类来说, 拷贝构造函数和拷贝赋值运算符没有合理的意义

    此种情况下, 定义类时必须采用某种机制阻止拷贝和赋值

    如iostream类和unique_ptr

    由于当我们未定义拷贝构造函数和拷贝赋值运算符时, 编译器会为它们生成合成版本, 我们需要将其定义为显式删除 =delete : 对其进行声明, 但不能以任何方式使用它们

  2. 当我们希望引导函数匹配过程时, 有时会将函数定义为删除 =delete

    我们只能对默认构造函数和拷贝控制成员使用 =default , 但可以对任何函数指定 =delete

=delete 必须出现在第一次声明时给出


=delete 出现之前

通过将拷贝构造函数和拷贝赋值运算符定义为私有 private 来阻止拷贝

存在漏洞: 类的其他函数成员和友元仍能使用拷贝构造函数和拷贝赋值运算符

因此, 只声明拷贝构造函数和拷贝赋值运算符为私有 private , 而不去定义它们

声明但不定义一个成员是合法的; 如果在代码中试图访问这些成员, 将导致链接时错误


删除 delete 的析构函数

如果某个类的析构函数为删除 delete , 无法销毁该类型对象

对于这种类, 编译器不允许定义该类型的临时对象; 我们可以定义该类型的动态对象, 但仍旧不能通过析构函数销毁动态对象, 除非使用删除器

如果某个类拥有这种数据成员, 编译器同样不允许定义该类型的临时对象; 我们可以定义该类型的动态对象, 但仍旧不能通过析构销毁动态对象, 除非使用删除器

未验证删除器

即定义一种只能创建该类型动态对象的类; 未验证