C++11 是 C++11的一次 "大版本更新",本文打算聊聊的就是更新内容中的 右值引用。
左值右值
有右就有左,有右值就有左值,那么什么是左值右值呢?
左值(lvalue) :有名字、有内存地址、能被取地址 & 的值 / 表达式
例子:变量、数组元素、解引用的指针
右值(rvalue):没有名字、临时的、不能被取地址的值 / 表达式
例子:字面量、表达式结果、函数返回的临时对象
这个东西看着挺奇怪的,但其实在写代码时一直都见到。
这里 a 是左值,1 是右值。
但是可千万不要当作是 "在左边" 就是左值,"在右边" 就是右值。
这里 a, b 其实都是左值。
左右值看的是 表达式的属性。
右值引用
以前用C++ 通常都是使用的 C++98,里面的 int& ,其实就是左值引用。而 右值引用,是 C++11 才引入的,在 C++98 里面没有,所以需要专门讲。
在传统的左值引用中,我们只能绑定左值。比如:

为了解决这个问题,右值引用便诞生了。

左值引绑定左值,右值引用绑定右值,但其实左值引用也可以绑定右值,右值引用也可以绑定左值。

move 的本质是进行强制类型转换。
右值引用的优势
右值引用被设计出来,那说明原来的左值引用在某些地方无法很好的满足需求。那我们先来了解或者说复习一下左值引用的主要作用。
左值引用的主要使用场景是在函数中左值引用传参 和左值引用传返回值时减少拷贝 ,同时还可以修改实参和修改返回对象的价值 。左值引用其实已经解决了大部分场景的拷贝效率问题,但有些场景是不能使用左值引用返回的。比如:当对象是在函数内部创建的时候,此时你如果想用左值引用该对象并返回,那么会产生悬空引用(野引用)。
如果没有右值引用,我们的解决办法是 返回 值,如果是这样,那我们就无法减少拷贝。那么右值引用是如何解决这个问题的呢?
首先要知道,这里的本质是返回对象是一个局部对象,函数结束这个对象就会被销毁,右值引用也无法改变对象被销毁的事实。但它利用一种特殊的构造函数------移动构造 ,局部对象会被编译器视为亡值(可移动的右值),将内部的堆内存资源转移给返回的对象。

像这段代码中,移动构造就是将 str 在堆中的资源转移给了 ret ,因为堆中的资源不会随着函数结束而销毁,于是就可以在不进行拷贝的情况下拿到函数的返回值。
可能有人会疑惑:test 中的 str 明明是左值啊,怎么变成右值了?前面已经提到:函数返回的临时对象是右值,所以,str 在函数内是一个左值,但是作为函数返回值时,它自动转换为了右值。
还有的疑问是:对于内置类型,没有堆资源,那怎么办呢?对于内置类型,只能使用传值返回,这是不可避免的,但它占用的空间很小,拷贝的开销可以忽略不计。
总结:
1.当返回值不是函数内定义的局部变量时,使用左值引用做返回值。
2.当返回值是 函数内定义的局部变量,并且是 内置类型时,使用 值 做返回值。
3.当返回值是 函数内定义的局部变量,并且不是内置类型时,使用右值 + 移动构造。
类型分类
前面提到了亡值,这其实是右值的一类,下面对 "值" 进行更进一步的分类。
左值:有名字、可取地址、能长久存在
纯右值:没名字、临时、不能取地址
亡值:即将销毁、可以被移动(move出来的)
右值:纯右值 + 亡值
泛左值:左值+亡值
总的来说,其实类型就两层:泛左值和右值。
引用折叠和完美转发
引用折叠
C++ 中不能直接定义引用的引用,比如:

这样写会直接报错,我们只能通过模板 和 typedef 中的类型操作来构成。
通过模板 和 typedef 中的类型操作来构成引用的引用时,C++11给出了一个引用折叠的规则:右值引用的右值引用折叠成右值引用,其他组合均折叠成左值引用。

例子:

也就是只要有一个是左值引用,那就是左值引用,否则为右值引用。
完美转发
首先了解一个概念:变量表达式都是左值属性。意味着:一个右值被右值引用绑定后,右值引用变量表达式的属性也是左值。
例如:

如果我们给 Func 传一个右值, func 得到的是左值。也就是如果给 Func 传入的是 数字 1, func 得到的其实是变量 t ,t 的值为 1 。
当我们希望保持 t 的属性不变,就需要使用到完美转发:

forward 的本质和 move 一样,其实都是强制类型转换。