左右值及引用
左值和右值
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的值之后是不确定的。