左右值及引用

左值和右值

C++中每个表达式要么是左值要么是右值。关于左值和右值的概念C++ Prime中的一句话是“当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存中的位置)”。在需要右值的地方,可以用左值来代替,实际使用是它的内容,但是右值不能当做左值来使用。

我们平时见到的大部分都是左值,赋值等号左边是左值,其运算结果也是左值。解引用运算符、下标运算符、递增、递减返回的都是左值。在代码中,你能看到有名字的变量基本都是左值

取地址符作用于左值,返回的是右值。字面常量也可以当做右值来使用。 当然这是一种右值形式。可以理解右值是一次性的对象。临时对象也算右值,它们在创建的地方不能被操作,并且很快就会销毁。其实,字面常量也是创建的临时变量。对于右值不太好理解的,就记住右值是一次性的。

左值引用和右值引用

左值引用就是绑定在左值上的引用,右值引用就是绑定在右值上的引用。不过有一点比较饶人,就是右值引用它本身是左值

int && rr1 = 32; // 正确, 字面常量是右值
int && rr2 = rr1; // 错误, 右值引用rr1本身是一个左值。(它也是有名字的变量)

模板和右值引用

template <typename T>
void f(T&& x) {}

模板参数推导会发生以下情况:

  • 如果f接受一个左值参数,那么x是一个左值引用
  • 如果f接受一个右值参数,那么x是一个右值引用

若想知道详细的推导机制,可以搜搜模板实参推断和引用、引用折叠等概念。关于上面这段代码还有一点要提的,就是右值引用本身是左值,右值是右值,右值引用是右值引用,不要搞混了,假设调用f时,实参是一个右值,在函数f体内,x是一个右值引用,右值引用就是左值,是左值就可以做对应的改动。比如:

template<typename T>
void f(T&& x) { cou << x++; }

f(2); // 3

再看下面这段代码:

template<typename T>
void g(T&& x) {}

template<typename T>
void f(T&& x) {
    g(x);
}

上面这段代码有一个问题就是,在调用f的时候,不管给f 传的是左值还是右值,在函数f体内,x就是一个左值(不管是左值引用还是右值引用),所以在左值传参给函数g时,在 g 的函数体内,x 就是是一个左值引用。如果想在 g 的函数体内的 x 是右值引用,那么需要使用标准库函数 std::forward来转发引用。将 f 函数改为:

tempalte<typename T>
void f(T&& x) {
    g(std::forward<T>(x));
}

std::forward 函数保持了 x 的引用类型,所以:

  • 如果 x 是一个右值引用,那么std::forward作用和std::move一样。
  • 如果 x 是一个左值引用,那么std::forward没做啥事。

std::move

虽然我们不能直接将右值引用绑定到左值上,但可以显示的将左值转换为对应的右值引用类型。通过std::move函数来获得绑定到左值上的右值引用。

int i = 3;
int && rr = std::move(i);

经过std::move函数操作后的对象内容是未定义的,可以认为内容被移走了。std::move本身接受任何类型参数,包括左值右值。

int i = 3, j;
j = std::move(2); // 从一个右值移动数据
j = std::move(i); //从一个左值移动数据,但是i的值之后是不确定的。

results matching ""

    No results matching ""