先要理解什么是C++的值
cppreference对Value做了解释,
C++11之前,lvalue,rvalue可以简单的理解为等号两边的值类型。但C++11后,语言对用户开放了更多的辅助类型以帮助设计者开发更好用的STL库。比较典型的就是引入glvalue(general left value), xvlaue( eXpiring value, for it is temporary value at most of time, it explained in stroustroup’s article), prvalue( pure right value).
lvalue: 有identity/address (这里的identity是指可以用来和其它值用来比较的一个addressable address。不能比较的表达式意味着没有Identity), 但是这个不能被move,转移它所拥有的值或指针。通常情况下见到的变量都是lvalue。
xvalue; 既有identity,又可以被move。比如函数的返回值。
prvalue: 没有identity,但可以被move。比如某个常量值。数字1,2,常量字符串 “rock”.
StackOverFlow有一个不错的图来解释左值和右值。

C++标准进一步解释了xvalue
判断一个表达式是xvalue的依据:
- 无论是显式还是隐式的函数调用,其返回值是对象类型的右值引用(rvalue reference to object type)
- 将右值引用强制转换(cast)为一个 对象类型。
- 非静态非引用类型数据成员访问表达式。如 func().m; a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue)
- 指向成员的指针表达式,如*pbm。pb是xvalue。 (a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.)
统一来讲,带名字的右值引用被当做左值,没名字的右值引用被当做xvalue;而如果引用的对象是函数的话,则不管有无名字都是左值。(In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether namedornot)
And as a follow up few examples are given. Here’s an extension to them:
struct A { int m; };
A&& operator+(A, A);
A a;
A b;
a + b; // xvalue
A&& f();
f(); // xvalue
f().m; // xvalue
A a;
static_cast<A&&>(a); // xvalue
std::move(a); // xvalue
It’s a good “Move”
move本身不对其中的值进行操作,它是配合STL模板函数来完成将A容器的引用指针数据直接转交给B容器。如:
std::vector A(2,3);
std::vector B(std::move(A));
转移发生在B的右值拷贝构造函数中: std::vector(std::vector&& a){ this.data_pointer = a.data_pointer; a.reset();}
xvalue的存在就像薛定谔的猫,在某些场景下,xvalue既可以左又可以右。(这里会列出更多场景)
forward仅仅是为了在参数传递过程中,保持&&类型或&类型而引入。毕竟我们要区分到底是一个 1,还是一个int i=1。
B.S对lvaue,rvalue的解释,我认为对一般人来说不好理解。特别在解释move的解释上偏晦涩。 在编程实现上,std::move/std::forward 这对兄弟可以用来理解URef,RRef,LRef之间的关系。
// move实现
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
// forward实现一个rvalue的类型传递
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
// forward实现一个lvalue的类型传递
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
类型推导
参数在推导传递过程中,允许退化(decay, reference collapse)。退化的场景有如下:
T&& -> T&
T&& & -> T&
T& && -> T&
T&& && -> T&&
因此当Foo(&)传入一个&&类型时,我们为了保持右值特性,使用forward保持。在Foo(&&)传入一个&类型时,退化为左值类型T&,也使用forward保持。
Scott Meryer 对这个问题展开了更有意思的解释。
他引入一个新的概念来解释这个令人费解的特性:URef (universal reference),把URef比作随着温度而决定性别的火鸡。
- T&& , auto&&, typedef T&&, decltype(T&&) T是一个符号,帮助类型推导。我们称它“通用引用”。 它既可以Bind一个左值(lvalue)后成为一个左值引用(LRef),也可以Bind一个右值(rvalue)成为一个右值引用(RRef)。[注意是 T]
- 那你说, vector::push_back(T&&)是一个URef吗? 不,vector的这个函数已在编译时实例化时确定了T,如vector::push_back(Widget&&), 所以是rvalue引用。
来看几个例子:
/*auto deduction example*/
vector<int> aVector(6); //aVectorp[5] 是一个典型的lvalue
auto&& aRef = aVector[5]; //推导为vector<int>&, URef绑定后成为LRef。
auto& bRef = aVector[5]; //推导为vector<int>&, LRef绑定LRef。
auto&& cRef = std::move(aVector); //推导为vector<int>&&, URef绑定rvalue成为RRef
int&& rri = 5; // 无推导,类型为int&&
auto autoRR = rri; // 推导为int, 即使rri是const int&& ,去掉const和&&
auto& autoRRR = rri; // 推导为int&
auto&& autoRRRR = rri; // 推导为int&
auto&& autoRRRRRR = autoRRR; //推导为int&
auto autoX = autoRRRRRR; //推导为int,即使autoRRRRRR是const int&&。
auto&& autoY = rri; // 按照collapse规则,应该&&,但是这里推导为&
// 按照Scott的解释,rri初始化URef已经成为一个LRef
/*decltype example*/
Widget w;
decltype(w)&& r1 = std::move(w); // r1 type is Widget&&
decltype((w))&& r2 = std::move(w); // (w) is a expression,return &, r2 is Widget&
总结起来为以下:
- type&& 并不等价于 右值引用(rvalue reference)
- type&& 用于 语法 + 类型推导,我们称之为通用引用(URef, Universal Reference.)
- URef最后是LRef还是RRef取决于初始化。LValue=>LRef, RValue =>RRef.
- 并不是所有模板的T&&是URef。比如Vector::push_back的&&参数版本。
- 重载URef通常是不对的。行为不是你所预期。
- 类型推导和引用退化(reference collapsing)是需要URef的基本场景:模板,auto,typedef, decltype。
腾讯技术中心发表的一篇总结文章: