标准库类、string和shared_ptr类既支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。
一、右值引用
必须绑定到右值的引用,通过&&来获得右值引用。
只能绑定到一个将要销毁的对象,因此我们可以自由地将一个右值引用的资源“移动”到另一个对象中。
不能将左值引用绑定到要求转换的表达式、字面常量或是返回右值的表达式。
右值引用则相反,可以绑定到这类表达式上,但不能讲一个右值引用直接绑定到一个左值上。
1 | int i = 42; |
返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符,都是返回左值。
返回非引用类型的函数,连同算数、关系、位以及后置递增/递减运算符,都生成右值。可以讲一个const的左值引用或者一个右值引用绑定到这类表达式上。
左值持久、右值短暂
左值有持久的状态,右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
由于右值引用只能绑定到临时对象,我们得知
所引用的对象将要被销毁
该对象没有其他用户
使用右值引用的代码可以自由地接管所引用的对象的资源。
变量是左值
把看成变量表达式,变量表达式都是左值。
1 | int &&rr1 = 42; |
标准库move函数
通过move获得绑定到左值上的右值引用。头文件utility。
1 | int &&rr3 = std::move(rr1); //正确 |
move告诉编译器:我们有一个左值,但希望像右值一样处理它。调用move就意味着承诺:除了对rr1赋值或销毁它外,将不再使用它。
二、移动构造函数和移动赋值运算符
从给定对象“窃取”资源。
移动构造函数第一个参数是该类型的一个右值引用。与拷贝函数一样,任何额外的参数都必须有默认实参。一旦资源完成移动,源对象必须不再指向被移动的资源,这些资源的所有权已经归属新创建的秀爱哪个。
1 | StrVec::StrVec(StrVec &&s)noexcept |
noexcept通知标准库构造函数不抛出任何异常。
移动赋值运算符
1 | StrVec &StrVec::operator=(StrVec &&rhs) noexcept |
移动后源对象必须可析构,例如指针成员设置为nullptr。
合成的移动操作
只有当一个类没有定义任何自己版本的拷贝控制成员,且类的每个非static数据成员都可以移动时,编译器才会为它合成移动构造函数或移动赋值运算符。
1 | struct X{ |
移动操作永远不会隐式定义为删除的。
1 | struct hasY |
如果类定义了一个移动构造函数和/或一个移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符会被定义为删除的。反之亦然。
移动右值,拷贝左值
1 | StrVec v1, v2; |
赋予v2的是getVec调用的结果。此表达式是一个右值。此情况下,两种赋值运算符都是可行的。调用拷贝复制运算符需要进行一次到const的转换,而StrVec&&则精准匹配。因此使用移动赋值运算符。
如果没有移动构造函数,右值也被拷贝
1 | class Foo{ |
move(x)返回一个绑定到x的Foo&&。Foo的拷贝构造函数是可行的,将一个Foo&&转换为一个const Foo&,因此z的初始化将使用Foo的拷贝构造函数。
如果一个类有一个可用的拷贝构造函数而没有移动构造函数,则其对象是通过拷贝构造函数来“移动”
的。
拷贝并交换赋值运算符和移动操作
1 | class HasPtr |
更新三/五法则
所有五个拷贝控制成员应该看作一个整体:一般来说,如果一个类定义了任何一个拷贝操作,它就应该定义所有五个操作。
三、右值引用和成员函数
定义了push_back的标准库容器提供两个版本:一个版本有一个右值引用参数,另一个版本有一个const左值引用。
1 | void push_back(const X&); //拷贝:绑定到任意类型的X |
第二个版本对于非const的右值是精确匹配的,因此当我们传递一个可修改的右值时,编译器会选择运行这个版本。
区分移动和拷贝的重载函数通常有一个版本接受const T&,另一个版本接受T&&
1 | class StrVec |
当我们调用push_back时,实参类型决定了新元素是拷贝还是移动。
1 | StrVec vec; |
引用限定符(&、&&)
可以使&或&&,分别指出this可以指向一个左值或右值。
1 | class Foo{ |
class Foo
{
public:
Foo someMem() & const; //错误:const限定符必须在前
Foo someMem() const &; //正确:const限定符在前
};
重载和引用函数
引用限定符也可以区分重载版本。我们可以综合引用限定符合和const来区分一个成员函数的重载版本。
1 | class Foo |
当我们定义两个或两个以上具有相同名字和相同参数列表的成员函数,就必须对所有函数都加上引用限定符,或所有都不加。
1 | class Foo{ |
如果一个成员函数有引用限定符,则具有相同参数列表的所有版本必须有引用限定符。