右值引用和万能引用都是C++11引入的新概念;由于都与&&有关系,所以这里将他们放到一个专题进行分析论述。
一般情况下,以赋值运算符=为界,=左边称左值lvalue,=右边称右值rvalue。例如:int value = 3;value 就是lvalue, 3就是rvalue。但这只是形式而已,不能完全概括左值和右值,因为在某些情况下,这条规则是不生效的。例如:int aValue = value; value 出现在=右边,但是value却不是右值。
除rvalue和lvalue外,现代C++还存在多个属性分类,他们是将亡值xvalue,纯右值prvalue和泛左值glvalue,彼此之间的关系如图一所示。
如图一所示,泛左值glvalue = 左值lvaue + 将亡值xvalue;右值rvalue = 纯右值prvalue + 将亡值xvalue。变化的是C++11之前的左值变为C++11标准中的纯右值;C++11的左值与以前的标准基本一致。
关于左值,C89标准有这样论述:the definition of lvalue as an object locator,也就是说一个可以取地址且不会立刻消亡的对象就是一个左值,所以lvalue中的l可理解为location。
纯右值prvalue则是C89标准的右值,其表示没有明确地址的只读对象,prvalue中的r可理解为read。纯右值要么是字面值常量;要么是求值结果相当于字面值或匿名临时变量。非引用返回的临时变量,表达式产生的临时变量,原始字面值,lambda表达式都是纯右值。
将亡值是C++11引入的与右值引用相关将要销毁的对象,可以被移动的值。左值和右值两种最明显的区别是:左值有持久的状态,而右值要么是纯右值(字面值,表达式求值过程中创建的临时对象)要么是将要消亡可移动的临时对象。
引用由C++98标准引入,由于引用只能绑定左值;所以Morden C++(C++11及以后标准)引入右值引用解决绑定右值的问题,右值引用可以说是对传统引用的扩充。经Morden C++扩充后,传统C++的引用,在Morden C++中变成左值引用;Morden C++引入右值引用,从而引出移动语义;
C++98标准引入左值引用,左值引用通过&定义,其绑定规则可总结如下:
具体可参考举例:
int b = 5;
int& a = b; // 可以正常编译
int& a1 = 500; // 无法通过编译
const int& a2 = 500; // 可以通过编译
所谓右值引用就是必须绑定到右值的引用,我们一般通过&&而不是&来获得右值引用。所以可看出右值引用可绑定到将要销毁的对象(将亡值),把将亡值绑定到右值引用相当于右值引用的资源移动到了一个对象,这样可延长将亡值的生命周期。例如:
struct Circle
{Circle(const double& circle) : circle(circle) {}double circle;
};Circle&& newCircle()
{return Circle(1.0);
}
除了将亡值以外右值引用还可以绑定纯右值(即字面值常量),例如:
double&& radius = 5.30;
右值引用可以绑定将亡值或纯右值,但是无法绑定左值。例如:
int b = 5;
int&& a = b; // 无法编译
常量右值引用,可以引用纯右值和将亡值,但是能绑定左值。例如:
int b = 5;
const int&& a = 100;
const int&& width = newCircle().circle;
const int&& c = b; // 无法编译
万能引用的英文是universal reference,除了“万能引用”,还有另外一种翻译“未定义引用”。 万能引用的一般定义方式为T&&,注意此处T为可演绎的模板类型,不是普通意义上的类型。万能引用一般存在于下述两种场景:
void f(int &&value) // “&&” means rvalue referenceint && var = someValue; // “&&” means rvalue referenceauto&& var2 = var1; // “&&” means universal referencetemplate
void f(std::vector&& param); // “&&” means rvalue referencetemplate
void f(T&& param); // “&&” mean universal reference
对于万能引用,需要注意的有两点:
为了更好的说明上述两个注意事项,我们举例分析:
例一:T与容器结合
template
void fun(std::vector&& arg) // “&&” means rvalue reference
此处,&&为右值引用,因为入参的声明类型为std::vector
例二:const T&&
template
void fun(const T&& arg); // “&&” means rvalue reference
由于存在const修饰T&&,导致此处&&也是右值引用,而非万能引用。
例三:成员函数T&&
template >
class vector
{
public:...void push_back(T&& x); // fully specified parameter type ⇒ no type deduction;... // && rvalue reference
};
虽然此处声明为T&&,但是在模板实例化时T类型就已经确定了,因此T不需要编译是类型推导。所以push_back声明中的&&为右值引用而非万能引用。
例四:变长参数模板Args&&... args
template >
class vector
{
public:...template void emplace_back(Args&&... args); // deduced parameter types ⇒ type deduction;... // && ≡ universal references
};
此举例中,虽然模板实例化时T的类型可确定,但是每个args的类型也还是需要逐个推导。因此此处&&为万能引用而非右值引用。
左值和右值在模板推导时是存在差异的,对于类型T的lvalue,模板会推导为T&类型;但是对于类型T的右值,模板会推导为T。
template
void func(T&& arg);...int x;...func(10); // invoke func on rvalue
func(x); // invoke func on lvalue
对于函数调用func(10),func
void func(int&& arg); // 右值变量func的实例化
但是对于func(x),func
void func(int& && arg); // 左值变量func的实例化
但是func(int& && arg)这是一个不合法的函数声明。为了解决这个问题C++11引入了“引用折叠”。
由于存在左值引用和右值,所以就会存在4形式的引用组合,他们分别是:lvalue reference to lvalue reference, lvalue reference to rvalue reference, rvalue reference to lvalue reference, and rvalue reference to rvalue reference。这四种形式的引用折叠规则:
模板入参声明 | 入参类型 | 引用折叠的类型 |
---|---|---|
lvalue reference | lvalue reference | lvalue reference |
lvalue reference | rvalue reference | lvalue reference |
rvalue reference | lvalue reference | lvalue reference |
rvalue reference | rvalue reference | rvalue reference |
同左值引用和右值引用一样,万能引用同样需要初始化。基于引用折叠规则,万能引用的初始化规则可总结如下:
int&& var1 = 100; // var1 is a rvalue reference, but var1 is a lvalue
auto&& var2 = var1; // var2 is an lvalue reference, since we can take the address of var1
综述所述,假设T为一个具体类型,那么关于引用可总结如下:
同样对应万能引用和引入折叠,C++11折叠规则更简单: