右值引用
所谓右值引用就是必须绑定到右值的引用,我们通过 && 而不是 & 来获得右值引用。
右值引用有一个很重要的性质--只能绑定到一个将要销毁的对象,即左值持久,右值短暂。
int i = 42; int& r = i; // 正确,r 引用 i int&& rr = i; // 错误,不能将一个右值引用绑定到一个左值上 int& r2 = i * 42; // 错误,i * 42 是一个右值 const int& r3 = i * 42; // 正确,可以将一个 const 的引用绑定到一个右值上 int&& rr2 = i * 42; // 正确,将 rr2 绑定到乘法结果上
标准库 move 函数
虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为 move 的新标准函数来获得绑定到左值的右值引用。
int&& rr3 = std::move(rr1); // ok
move 调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它,我们必须认识到,调用 move 就意味着:除了对 rr1 赋值或销毁它以外,我们将不再使用它,在调用 move 之后, 我们不能对移后源对象的值做任何假设。
移动构造函数&移动赋值运算符
移动构造函数和移动赋值运算符“窃取”资源,它们通常不分配任何资源。因此这两类函数通常不会抛出任何异常,当不抛出异常时必须声明为 noexcept。
Foo(Foo&& v) noexcept { // 移动构造函数 ; // TODO } Foo& operator = (Foo&& a) noexcept { // 移动操作运算符 ; // TODO return *this; }
用拷贝构造函数代替移动构造函数几乎肯定是安全的,用赋值运算符代替移动赋值运算符也几乎肯定是安全的。
Foo x; Foo y(x); // 拷贝构造函数,x是一个左值 Foo z(std::move(x)); // 拷贝构造函数,因为未定义移动构造函数
在对 z 进行初始化时,我们调用了 move(x),它返回一个绑定到 x 的 Foo&&。Foo 拷贝构造函数是可行的,因为我们可以将一个 Foo&& 转换为一个 const Foo&。因此 z 的初始化将使用 Foo 的拷贝构造函数。
引用限定符
用来指出一个非 static 成员函数可以用于左值或右值的符号。限定符 & 和 && 应该放在参数列表之后或 const 之后(如果有的话)。被 & 限定的函数只能用于左值,被 && 限定的函数只能用于右值。
Foo x; Foo y(x); // 拷贝构造函数,x是一个左值 Foo z(std::move(x)); // 拷贝构造函数,因为未定义移动构造函数
代码示例
class A { public: int val = 0; A() { std::cout << "A()" << std::endl; } A(int v) { std::cout << "A(int v)" << std::endl; } A& operator = (const A& a) { std::cout << "A& operator = (const A& a)" << std::endl; return *this; } A(const A& a) { std::cout << "A(const A& a)" << std::endl; } A& operator = (A&& a) { std::cout << "A& operator = (A&& a)" << std::endl; return *this; } A(A&& v) { std::cout << "A(A&& v)" << std::endl; } virtual ~A() { std::cout << "~A()" << std::endl; } void print(int a) && { // 如果只有此函数,c.print(3);编译报错 std::cout << "print(int a) &&" << std::endl; } void print(int a) & { // 如果只有此函数,std::move(d).print(2);编译报错 std::cout << "print(int a) &" << std::endl; } }; int main() { A b; // A() A c; // A() A d; // A() c = b; // A& operator = (const A& a) d = std::move(b); // A& operator = (A&& a) std::move(d).print(2); // print(int a) && c.print(3); // print(int a) & // ~A() // ~A() // ~A() return 0; }