右值引用

右值引用（rvalue reference），是C++程序设计语言自C++11标准提出的一类数据类型。用于实现移动语义（move semantic）与完美转发（perfect forwarding）。
背景.
在C++11提出右值引用之前，C++03及更早的C++标准中，表达式的“值分类”（value categories）属性为左值或右值。左值是对应（refer to）内存中有确定存储地址的对象之表达式的值，而右值是所有不是左值之表达式的值。因而，右值可以是字面量、临时对象等表达式。能否被赋值不是区分C++左值与右值的依据，C++的const左值是不可赋值的；而作为临时对象的右值可能允许被赋值。左值与右值的根本区别在于是否允许取址运算符（&）获得对应的内存地址。C++03及以前的标准定义了在表达式中左值到右值的三类隐式自动转换：
作为一种追求执行效率的语言，C++在用临时对象或函数返回值给左值对象赋值时的深度拷贝（deep copy）一直受到诟病。考虑到临时对象的生命期仅在表达式中持续，如果把临时对象的内容直接移动（move）给被赋值的左值对象，效率改善将是显著的。这就是移动语义的来源。
与传统的拷贝赋值运算符（copy assignment）成员函数、拷贝构造（copy ctor）成员函数对应，移动语义需要有移动赋值（move assignment）成员函数、移动构造（move ctor）成员函数的实现机制。可以通过函数重载来确定是调用拷贝语义还是移动语义的实现。
右值引用就是为了实现移动语义与完美转发所需要而设计出来的新的数据类型。右值引用的实例对应于临时对象；右值引用并区别于左值引用，用作形参时能重载辨识（overload resolution）是调用拷贝语义还是移动语义的函数。
无论是传统的左值引用还是C++11引进的右值引用，从编译后的反汇编层面上，都是对象的存储地址与自动解-{}-引用（dereference，取址的相反）。因此，右值引用与左值引用的变量都不能悬空(dangling)，也即定义时必须初始化从而绑定到一个对象上。右值引用变量绑定的对象，是编程者认为可以通过移动语义移走其内容的对象，对这种对象就需要定义为一种独特的值分类，即C++11标准称之为“临终值”（eXpire Value）。临终值对象既有存储地址因此可以绑定到右值引用变量上，而且它又是一个即将停止使用的对象可以被移走内容。所以临终值既不同于左值，也不同于传统的右值（C++11称之为纯右值），不能取地址运算（&）。另一方面，临终值兼有传统的左值与右值的性质：既对应于一个（临时）对象，称之为有标识（identity）；同时其内容可以移走，称之为可移动性（movability）。C++11标准把临终值与左值合称为广义左值，即指向某个物理存在的对象；把临终值与纯右值（对应C++03时的右值概念）合称为右值（C++11重新定义的概念），其内容可以移走（该右值生命期到此为止，此后将不再使用）。之所以称为右值而不叫做广义右值，是因为右值引用既可以与临终值对象绑定，也可以与纯右值对象绑定（这时往往自动生成一个临时对象）。
C++语言在引入了右值引用之后，面临着一个问题：如何让编程者指出哪个对象具有临终值？这有两种显式指定方法：如果函数（或运算符）的返回类型为右值引用，或者通过类型转换如static_cast<Type&&>或者std::move()模板函数。
定义.
设X为任何一种数据类型，则定义X&&是到数据类型X的右值引用（rvalue reference to X）。传统的引用类型X&被称为左值引用（lvalue reference to X）。例如：
语义.
右值引用是一种数据类型，既有右值性质，也有左值性质。在C++11中为右值引用专门定义了临终值（eXpiring value）这一概念。右值引用很类似于传统的左值引用。
例如：
<syntaxhighlight lang="cpp">
int s=101;
int&& foo(){return static_cast<int&&>(s);} //返回值为右值引用
int main() {
</syntaxhighlight>
C++中，引用（reference）是指绑定到内存中的相应对象上。左值引用是绑定到左值对象上；右值引用是绑定到临时对象上。这里的左值对象是指可以通过取地址&运算符得到该对象的内存地址；而临时对象是不能用取地址&运算符获取到对象的内存地址。
C++11有如下引用绑定规则：
对上述引用绑定规则，值得注意的是：
由于右值引用主要针对移动语义用来修改被引用的对象的内容，所以常量右值引用（const X&&）较少用到。
函数返回值是右值数据类型还是右值引用类型，区别在于前者是“传值”，后者是“传引用”可以修改被引用的对象。例如：
<syntaxhighlight lang="cpp">
int i = 101, j = 101;
int main()
 set(foo());
 std::cout <
上述例子中的函数set中可以对类型为右值引用的形参k赋值，这是因为C++标准规定，具名的右值引用被当作左值。这一规定的意义在于，右值引用本来是用于实现移动语义，因而需要绑定一个对象的内存地址，然后具有修改这一对象内容的权限，这些操作与左值绑定完全一样。右值绑定与左值绑定的分别在于函数重载时的行为。对于移动构造成员函数与移动赋值运算符成员函数，其形、实参数结合时是按照右值引用处理；而在这两个成员函数体内部，由于形参都是具名的，因而都被当作左值，这就可以用该形参来修改传入对象的内部状态。另外，右值引用作为xvalue（临终值）本来是用于移动语义中一次性搬空其内容。具名使其具有更为持久的生存期，这是危险的，因而规定具名后为左值引用，除非程序显式指定其类型强制转换为右值引用。
C++11标准给了使用者更大的决定权，例如把左值或临终值，转化为右值引用。这是通过定义在C++标准程序库codice_2中的codice_3实现的。codice_3是个模板函数，把输入的左值或右值转换为右值引用类型的临终值。其核心是强制类型转换codice_5语句。
除了左值引用、右值引用，对于函数参数类型是到模板参数类型的右值引用的情形，称之为广义引用（universal reference）或转发引用（forward reference）。例如：
详见完美转发一节的讨论。
完美转发.
完美转发也是C++11标准引入右值引用这一概念所要实现的目标之一。
背景.
在C++程序设计中，一个常见的类工厂函数，如下例：
<syntaxhighlight lang="cpp">
template <typename T, typename Arg> 
shared_ptr<T> factory(Arg arg)
</syntaxhighlight>
参数对象arg在上例中是传值方式传递，这带来了生成额外的临时对象的代价。对于类工厂函数，完美的参数传递应该是引用方式传递。因而，在codice_6中，参数是左值引用：
<syntaxhighlight lang="cpp">
template <typename T, typename Arg> 
shared_ptr<T> factory(Arg& arg)
</syntaxhighlight>
这种实现的问题是形参不能绑定右值实参。如codice_7将编译报错。进一步解决办法是按常量引用方式传递参数，如下例：
<syntaxhighlight lang="cpp">
template <typename T, typename Arg> 
shared_ptr<T> factory(const Arg& arg)
</syntaxhighlight>
这种实现的问题是不能支持移动语义。
形参使用右值引用可以解决完美转发问题。
引用折叠规则.
对于C++语言，不可以在源程序中直接对引用类型再施加引用。codice_8将编译报错。C++11标准中仍然禁止上述显式对引用类型再施加引用，但如果在上下文环境中（包括模板实例化、typedef、auto类型推断等）如出现了对引用类型再施加引用，则施行引用塌缩规则（reference collapsing rule）：
模板参数类型推导.
对函数模板codice_9，应用上述引用塌缩规则，可推导出如下结论：
这还适用于类模板的成员函数模板的类型推导：
<syntaxhighlight lang="cpp">
template <class T > class vector {
</syntaxhighlight>
函数模板的形参必须是T&&形式，才需要模板参数类型推导。即使形参声明为const T&&形式，就只能按字面意义使用，不需要模板参数类型推导。
<syntaxhighlight lang="cpp">
template<typename T>void f(const T&& param); // 这里的“&&”不需要类型推导，意味着“常量类型T的右值引用”
template<typename T>void f(std::vector<T>&& param); // 这里的“&&”不需要类型推导，意味着std::vector<T>的右值引用
</syntaxhighlight>
完美转发的解决方案.
<syntaxhighlight lang="cpp">
template <typename T, typename Arg> 
shared_ptr<T> factory(Arg&& arg)
</syntaxhighlight>
其中codice_10是定义在C++标准程序库codice_2中的模板函数：
<syntaxhighlight lang="cpp">
template< class T > T&& forward( typename std::remove_reference<T>::type& t )
template< class T > T&& forward( typename std::remove_reference<T>::type&& t )
</syntaxhighlight>
codice_10在调用时，应当显示给出该函数模板参数类型。其用途是：如果函数forward的实参的数据类型是左值引用，则返回类型为左值引用；如果函数forward的实参的数据类型是右值引用，则返回类型为右值引用，返回值的分类属于临终值，从而把参数的信息完整地传递给下一级被调用的函数。从上述codice_10的定义实现来看，实参必须是个为左值的引用对象，但是实参的数据类型有两种可能：
类似的，codice_3也是利用上述模板函数类型推导规则，定义在C++标准程序库codice_2中，无论输入的实参是左值还是右值，均返回右值引用：
<syntaxhighlight lang="cpp">
template<class T>
typename std::remove_reference<T>::type&& move(T&& a) noexcept
</syntaxhighlight>
定义在C++标准程序库codice_18中codice_19是个类模板，其中定义的类型codice_20是引用的基类型:
<syntaxhighlight lang="cpp">
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
</syntaxhighlight>
移动语义与异常.
移动语义只是把资源从一个右值对象搬移到被构造或者被赋值对象内部，因此保证不抛出异常是容易实现的。在codice_21这样的容器内实现移动语义，必须显式声明容器元素类的移动构造成员函数、移动赋值运算符成员函数不抛出异常。
右值引用的类型推导.
C++中涉及到的右值引用的类型推导，除了上述模板参数类型推导，还有：
auto关键字的类型完美转发.
C++11使用auto声明变量时，如：codice_22“auto&&”并不意味着这一定是右值引用类型的变量，而是类似于模板函数参数的类型推导，既可能是左值引用，也可能是右值引用。其目的是把初始化表达式的值分类情况，完美转发给由auto声明的变量。也即：
<syntaxhighlight lang="cpp">
Type1&& var1=anotherType1Instance; // var1的类型是右值引用，但是作为左值
auto&& var2=var1; //var2的类型是左值引用
std::vector<int> v;
auto&& val = v// std::vector::operator[的返回值是元素左值，所以val的类型是左值引用
Widget makeWidget(); // 类工厂函数
Widget&& var1 = makeWidget() // var1的类型是右值引用，具有左值。
Widget var2 = static_cast<Widget&&>(var1); // var2在初始化时可以使用移动构造函数。
</syntaxhighlight>
typedef的类型推导.
typedef也可能会用到引用塌缩规则。例如：
<syntaxhighlight lang="cpp">
template<typename T> class Widget {
};
Widget<int&&> w; // LvalueRefType的类型为int&
void f(Widget<int&>::LvalueRefType&& param); //param的类型为int&
</syntaxhighlight>
decltype类型推导.
decltype也可能会用到引用塌缩规则。例如：
<syntaxhighlight lang="cpp">
int var;
decltype(var)&& v1=std::move(var); //类型是int&&
</syntaxhighlight>