11.4 右值引用

简介

编码规范:只有在定义移动构造函数与移动赋值操作时使用右值引用,不要使用std::forward功能函数,你可能会使用std::move来表示将值从一个对象移动而不是复制到另一个对象。

右值引用指的是必须绑定到右值的引用,我们可以通过&&来获取右值引用。右值引用一个重要的性质就是只能绑定到一个将要销毁的对象,因此我们可以自由地讲一个右值引用的资源“移动”到另一个对象中去。

Tips:正常来说左值引用只能绑定到左值上,右值引用只能绑定到右值上,但是我们可以将一个const的左值引用绑定到一个右值上。

int i = 42;
int &r = i;              // 正确: 左值引用
int &&rr = i;            // 错误: 不能将一个右值引用绑定到一个左值上
int &r2 = i * 42;        // 错误: 不能讲一个左值引用绑定到一个右值上
const int &r3 = i * 42;  // 正确: 我们可以将一个const的左值引用绑定到右值上
int &&rr2 = i * 42;      // 正确: 右值引用

需要注意的是我们不能将一个右值引用绑定到一个右值引用类型的变量上:

// 正确: 字面常量是右值, 可以被右值引用
int &&rr1 = 42;
// 错误: 变量是左值, 我们不能讲一个右值引用绑定到一个变量上, 即使这个变量是右值引用类型也不行
int &&rr2 = rr1; 

标准库move函数获得右值引用

虽然我们不能将一个右值引用直接绑定到左值上,但我们可以使用utility头文件中的std::move函数来获得绑定到左值上的右值引用:

int &&rr1 = 42;
int &&rr2 = std::move(rr1);

需要注意的是:

  • 使用move的代码应该明确使用std::move而不是在提供using声明后使用move函数,防止和用户程序定义的接受单一形参的move函数冲突

  • 调用std::move就意味着承诺除了对原来的左值对象赋值或销毁它外,我们将不再使用这个对象的值

接受右值引用参数的成员函数

除了构造函数和赋值运算符外,一个成员函数也可以同时提供拷贝和移动版本(一个版本接受指向const的左值引用,一个版本指向非const的右值引用):

// 定义push_back的标准库容器提供两个版本: 右值引用参数和const左值引用参数
void push_back(const T&);  // 拷贝: 绑定到任意类型的T
void push_back(T&&);       // 移动: 只能绑定到类型T的可修改的右值

Tips:一般情况下我们不需要为函数定义接受一个const T&&或是一个普通的T&参数的版本。因为当我们希望从实参“窃取”数据时,通常传一个右值引用参数且该实参不能是const的,当我们希望从一个对象拷贝数据时,通常不需要定义一个接受普通T&参数的版本。

成员函数的右值引用限定符

通常情况下我们在一个对象上调用成员函数不需要管该对象是一个左值还是右值,比如:

string s1 = "tomo", s2 = "cat";
auto n = (s1 + s2).find('a');  // 在一个string右值上调用find成员
s1 + s2 = "test";              // 对一个string右值赋值

在旧标准中我们无法阻止上述这种使用方式,为了维持向后兼容性,新标准库类仍然允许向右值赋值。但是在新标准中如果我们想在自定义的类中阻止这种用法,可以强制左侧运算对象(即this指针指向的对象)是一个左值。

class Foo {
 public:
    // 左值引用限定符: 拷贝赋值运算符只可向可修改的左值赋值
    Foo &operator=(const Foo &rhs) & {
        return *this;
    }
};

Foo a, b;
a = b;             // 正确
std::move(a) = b;  // 错误: 不可向右值赋值

和左值引用限定符用法相似,右值引用限定符&&表示该成员函数只可应用于右值。我们可以综合引用限定符和const限定符来区分一个成员函数的重载版本:

class Foo {
 public:
    // 用于类型Foo的可改变的右值: 提高排序性能
    Foo sorted() && {
        // 本对象为右值, 可以原址排序
        std::sort(data_.begin(), data_.end());
        return *this;
    }
    // 可用于任何类型的Foo
    Foo sorted() const & {
        // 本对象是const或者一个左值, 不能原址排序
        Foo ret(*this);
        std::sort(ret.data_.begin(), ret.data_.end());
        return ret;
    }
 private:
    std::vector<int> data_;
};