六一的部落格


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



移动操作
移动构造函数
移动赋值运算符

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

  1. 要求合成版本为 default

    =default

    编译器一定会提供合成版本, 但不一定是 default ; 可能是 delete

  2. 要求合成版本为 delete

    =delete

    编译器一定可以满足


编译器提供 default 的合成版本需要满足的条件

非static数据成员类型
移动构造函数 有移动构造函数, 且可访问;
有析构函数
移动赋值运算符 有移动赋值运算符, 且可访问;
数据成员非引用, 均不具有顶层const

隐式 default

编译器提供 default 的合成版本

移动构造函数 未定义拷贝构造函数, 且满足编译器提供 default 的合成版本的条件
移动赋值运算符 未定义拷贝赋值运算符, 且满足编译器提供 default 的合成版本的条件

显式 default

显式要求编译器提供 =default 的合成版本, 且满足编译器提供 default 的合成版本的条件

编译器提供 default 的合成版本


显式 delete

  1. 显式要求编译器提供 =default 的合成版本, 但不满足编译器提供 default 的合成版本的条件
  2. 显式要求编译器提供 =delete 的合成版本

隐式 delete

移动操作永远不会隐式定义为删除 delete


显式指定合成版本

1class T
2{
3public:
4    T(T &&) = default;
5
6    T &operator=(T &&) = default;
7};
1class T
2{
3public:
4    T(T &&) = delete;
5
6    T &operator=(T &&) = delete;
7};

以上合成版本均为隐式内联 inline


std::move

编译器可以移动内置类型数据成员

如果数据成员为类类型, 而类类型有移动构造函数, 编译器也可以移动该类型数据成员


移动操作和拷贝操作

  1. 类类型只给出了移动操作的定义: 拷贝操作会被定义为删除
  2. 类类型只给出了拷贝操作的定义: 编译器不会主动提供移动操作的合成版本

定义了移动操作的类必须也定义对应的拷贝操作


显式要求编译器提供合成版本, 等价于定义了该操作

无论是要求 default 还是要求 delete


示例

  1. 要求合成版本为 default

     1#include <iostream>
     2
     3using namespace std;
     4
     5class Foo
     6{
     7public:
     8    Foo(int aa) : a(aa) {}
     9    Foo() : Foo(0) {}
    10    Foo(Foo &&) = default;
    11
    12private:
    13    int a;
    14};
    15
    16int main()
    17{
    18    Foo f1(5);
    19    Foo f2(f1);
    20
    21    return 0;
    22}

    报错: 由于用户声明了移动构造函数, 拷贝构造函数被定义为隐式删除

    error: call to implicitly-deleted copy constructor of 'Foo'
    Foo f2(f1);
        ^  ~~
    note: copy constructor is implicitly deleted because 'Foo' has a user-declared move constructor
    Foo(Foo &&) = default;
    ^
    1 error generated.
  2. 要求合成版本为 delete

     1#include <iostream>
     2
     3using namespace std;
     4
     5class Foo
     6{
     7public:
     8    Foo(int aa) : a(aa) {}
     9    Foo() : Foo(0) {}
    10    Foo(Foo &&) = delete;
    11
    12private:
    13    int a;
    14};
    15int main()
    16{
    17    Foo f1(5);
    18    Foo f2(f1);
    19
    20    return 0;
    21}

    报错: 由于用户声明了移动构造函数, 拷贝构造函数被定义为隐式删除

    error: call to implicitly-deleted copy constructor of 'Foo'
    Foo f2(f1);
        ^  ~~
    note: copy constructor is implicitly deleted because 'Foo' has a user-declared move constructor
    Foo(Foo &&) = delete;
    ^
    1 error generated.

如果定义了除默认构造函数外的构造函数, 类不会有默认构造函数

注意: 不是默认构造函数被定义为删除 delete


示例

 1#include <iostream>
 2
 3using namespace std;
 4
 5class Foo
 6{
 7public:
 8    Foo(const Foo &) = default;
 9
10private:
11    int a;
12};
13int main()
14{
15    Foo f1;
16
17    return 0;
18}

报错: 只有拷贝构造函数

error: no matching constructor for initialization of 'Foo'
Foo f1;
    ^
note: candidate constructor not viable: requires 1 argument, but 0 were provided
Foo(const Foo &) = default;
^
1 error generated.

如果类类型没有移动拷贝构造函数, 函数匹配规则会保证该类型的对象会被拷贝

使用std::move时, 返回一个右值; 而const &可以绑定一个右值

 1class Foo
 2{
 3public:
 4    Foo() = default;
 5    Foo(const &Foo);
 6};
 7
 8Foo x;                // 默认构造函数
 9Foo y(x);             // 拷贝构造函数
10Foo z(std::move(x));  // 拷贝构造函数: 未定义移动构造函数

拷贝并交换赋值运算符和移动操作

  1. 为HasPtr定义了友元swap
    1inline void swap(HasPtr &lhs, HasPtr &rhs)
    2{
    3    swap(lhs.ps, rhs.ps);
    4    swap(lhs.i, rhs.i);
    5}
  2. 在赋值运算符中使用了swap
    1HasPtr &HasPtr::operator=(HasPtr rhs)
    2{
    3    swap(*this, rhs);
    4    return *this;
    5}
  3. 为HasPtr定义移动构造函数
    1inline HasPtr::HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) { p.ps = 0; }
  4. 使用HasPtr
    1hp1 = hp2;              // hp2为左值, 赋值运算符传参时, 匹配拷贝构造函数
    2
    3hp1 = std::move(hp2);   // std::move返回一个右值, 优先匹配移动构造函数
    
    赋值运算符既是拷贝赋值运算符, 也是移动赋值运算符

更新三/五法则

如果类定义了5个操作中的任何一个, 它就应该定义所有5个操作


移动操作的合成版本


移动操作
移动构造函数
移动赋值运算符

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

  1. 要求合成版本为 default

    =default

    编译器一定会提供合成版本, 但不一定是 default ; 可能是 delete

  2. 要求合成版本为 delete

    =delete

    编译器一定可以满足


编译器提供 default 的合成版本需要满足的条件

非static数据成员类型
移动构造函数 有移动构造函数, 且可访问;
有析构函数
移动赋值运算符 有移动赋值运算符, 且可访问;
数据成员非引用, 均不具有顶层const

隐式 default

编译器提供 default 的合成版本

移动构造函数 未定义拷贝构造函数, 且满足编译器提供 default 的合成版本的条件
移动赋值运算符 未定义拷贝赋值运算符, 且满足编译器提供 default 的合成版本的条件

显式 default

显式要求编译器提供 =default 的合成版本, 且满足编译器提供 default 的合成版本的条件

编译器提供 default 的合成版本


显式 delete

  1. 显式要求编译器提供 =default 的合成版本, 但不满足编译器提供 default 的合成版本的条件
  2. 显式要求编译器提供 =delete 的合成版本

隐式 delete

移动操作永远不会隐式定义为删除 delete


显式指定合成版本

1class T
2{
3public:
4    T(T &&) = default;
5
6    T &operator=(T &&) = default;
7};
1class T
2{
3public:
4    T(T &&) = delete;
5
6    T &operator=(T &&) = delete;
7};

以上合成版本均为隐式内联 inline


std::move

编译器可以移动内置类型数据成员

如果数据成员为类类型, 而类类型有移动构造函数, 编译器也可以移动该类型数据成员


移动操作和拷贝操作

  1. 类类型只给出了移动操作的定义: 拷贝操作会被定义为删除
  2. 类类型只给出了拷贝操作的定义: 编译器不会主动提供移动操作的合成版本

定义了移动操作的类必须也定义对应的拷贝操作


显式要求编译器提供合成版本, 等价于定义了该操作

无论是要求 default 还是要求 delete


示例

  1. 要求合成版本为 default

     1#include <iostream>
     2
     3using namespace std;
     4
     5class Foo
     6{
     7public:
     8    Foo(int aa) : a(aa) {}
     9    Foo() : Foo(0) {}
    10    Foo(Foo &&) = default;
    11
    12private:
    13    int a;
    14};
    15
    16int main()
    17{
    18    Foo f1(5);
    19    Foo f2(f1);
    20
    21    return 0;
    22}

    报错: 由于用户声明了移动构造函数, 拷贝构造函数被定义为隐式删除

    error: call to implicitly-deleted copy constructor of 'Foo'
    Foo f2(f1);
        ^  ~~
    note: copy constructor is implicitly deleted because 'Foo' has a user-declared move constructor
    Foo(Foo &&) = default;
    ^
    1 error generated.
  2. 要求合成版本为 delete

     1#include <iostream>
     2
     3using namespace std;
     4
     5class Foo
     6{
     7public:
     8    Foo(int aa) : a(aa) {}
     9    Foo() : Foo(0) {}
    10    Foo(Foo &&) = delete;
    11
    12private:
    13    int a;
    14};
    15int main()
    16{
    17    Foo f1(5);
    18    Foo f2(f1);
    19
    20    return 0;
    21}

    报错: 由于用户声明了移动构造函数, 拷贝构造函数被定义为隐式删除

    error: call to implicitly-deleted copy constructor of 'Foo'
    Foo f2(f1);
        ^  ~~
    note: copy constructor is implicitly deleted because 'Foo' has a user-declared move constructor
    Foo(Foo &&) = delete;
    ^
    1 error generated.

如果定义了除默认构造函数外的构造函数, 类不会有默认构造函数

注意: 不是默认构造函数被定义为删除 delete


示例

 1#include <iostream>
 2
 3using namespace std;
 4
 5class Foo
 6{
 7public:
 8    Foo(const Foo &) = default;
 9
10private:
11    int a;
12};
13int main()
14{
15    Foo f1;
16
17    return 0;
18}

报错: 只有拷贝构造函数

error: no matching constructor for initialization of 'Foo'
Foo f1;
    ^
note: candidate constructor not viable: requires 1 argument, but 0 were provided
Foo(const Foo &) = default;
^
1 error generated.

如果类类型没有移动拷贝构造函数, 函数匹配规则会保证该类型的对象会被拷贝

使用std::move时, 返回一个右值; 而const &可以绑定一个右值

 1class Foo
 2{
 3public:
 4    Foo() = default;
 5    Foo(const &Foo);
 6};
 7
 8Foo x;                // 默认构造函数
 9Foo y(x);             // 拷贝构造函数
10Foo z(std::move(x));  // 拷贝构造函数: 未定义移动构造函数

拷贝并交换赋值运算符和移动操作

  1. 为HasPtr定义了友元swap
    1inline void swap(HasPtr &lhs, HasPtr &rhs)
    2{
    3    swap(lhs.ps, rhs.ps);
    4    swap(lhs.i, rhs.i);
    5}
  2. 在赋值运算符中使用了swap
    1HasPtr &HasPtr::operator=(HasPtr rhs)
    2{
    3    swap(*this, rhs);
    4    return *this;
    5}
  3. 为HasPtr定义移动构造函数
    1inline HasPtr::HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) { p.ps = 0; }
  4. 使用HasPtr
    1hp1 = hp2;              // hp2为左值, 赋值运算符传参时, 匹配拷贝构造函数
    2
    3hp1 = std::move(hp2);   // std::move返回一个右值, 优先匹配移动构造函数
    
    赋值运算符既是拷贝赋值运算符, 也是移动赋值运算符

更新三/五法则

如果类定义了5个操作中的任何一个, 它就应该定义所有5个操作