移动操作的合成版本
2023年12月31日 2024年1月1日
移动操作 |
---|
移动构造函数 |
移动赋值运算符 |
显式要求编译器提供合成版本
-
要求合成版本为
default
=default
编译器一定会提供合成版本, 但不一定是
default
; 可能是delete
-
要求合成版本为
delete
=delete
编译器一定可以满足
编译器提供 default
的合成版本需要满足的条件
非static数据成员类型 | |
---|---|
移动构造函数 | 有移动构造函数, 且可访问; |
有析构函数 | |
移动赋值运算符 | 有移动赋值运算符, 且可访问; |
数据成员非引用, 均不具有顶层const |
隐式 default
编译器提供 default
的合成版本
移动构造函数 | 未定义拷贝构造函数, 且满足编译器提供 default 的合成版本的条件 |
移动赋值运算符 | 未定义拷贝赋值运算符, 且满足编译器提供 default 的合成版本的条件 |
显式 default
显式要求编译器提供 =default
的合成版本, 且满足编译器提供 default
的合成版本的条件
编译器提供 default
的合成版本
显式 delete
- 显式要求编译器提供
=default
的合成版本, 但不满足编译器提供default
的合成版本的条件 - 显式要求编译器提供
=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
编译器可以移动内置类型数据成员
如果数据成员为类类型, 而类类型有移动构造函数, 编译器也可以移动该类型数据成员
移动操作和拷贝操作
- 类类型只给出了移动操作的定义: 拷贝操作会被定义为删除
- 类类型只给出了拷贝操作的定义: 编译器不会主动提供移动操作的合成版本
定义了移动操作的类必须也定义对应的拷贝操作
显式要求编译器提供合成版本, 等价于定义了该操作
无论是要求 default
还是要求 delete
示例
-
要求合成版本为
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.
-
要求合成版本为
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)); // 拷贝构造函数: 未定义移动构造函数
拷贝并交换赋值运算符和移动操作
- 为HasPtr定义了友元swap
1inline void swap(HasPtr &lhs, HasPtr &rhs) 2{ 3 swap(lhs.ps, rhs.ps); 4 swap(lhs.i, rhs.i); 5}
- 在赋值运算符中使用了swap
1HasPtr &HasPtr::operator=(HasPtr rhs) 2{ 3 swap(*this, rhs); 4 return *this; 5}
- 为HasPtr定义移动构造函数
1inline HasPtr::HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) { p.ps = 0; }
- 使用HasPtr
1hp1 = hp2; // hp2为左值, 赋值运算符传参时, 匹配拷贝构造函数 2 3hp1 = std::move(hp2); // std::move返回一个右值, 优先匹配移动构造函数
赋值运算符既是拷贝赋值运算符, 也是移动赋值运算符
更新三/五法则
如果类定义了5个操作中的任何一个, 它就应该定义所有5个操作