# 右值引用 移动语意
~~~
右值引用解决了左值引用无法传递临时对象和常引用传递的对象只读的问题. 右值引用允许传递一个可变的临时对象引用.
移动构造使用移动而非赋值语义完成构造过程, 主要用于解决函数返回值时的大量深拷贝开销.
#include
int main(void)
{
int a = 30;
int &b = a;
//int &&c = a; /* error 右值引用不能绑定到左值 */
//int &d = a * 2; /* erro a * 2 是一个右值 */
const int e = a * 2; /* const 的引用可以绑定到一个右值 */
int && f = a * 2; /* 右值引用可以绑定到右值 */
//int && g = f; /* error 不能将一个右值引用绑定到一个变量上, 即使这个变量是右值引用类型也不行 */
int && h = std::move(f); /* ok */
return 0;
}
int r1 = 20;
int &&r2 = std::move(r1);
调用move就意味着承诺: 除了对r1赋值或销毁它以外, 将不能再使用它.
~~~
# 左值引用和右值引用
```
std::move(a) 返回变量右值
std::ref(a) 返回变量引用 主要是考虑函数式编程(如std::bind)在使用时,是对参数直接拷贝
```
# 完美转发
```
std::forward可以保存参数的左值或右值特性
```
# std::move()和std::forward()对比
* std::move执行到右值的无条件转换。就其本身而言,它没有move任何东西。
* std::forward只有在它的参数绑定到一个右值上的时候,它才转换它的参数到一个右值。
* std::move和std::forward只不过就是执行类型转换的两个函数;std::move没有move任何东西,std::forward没有转发任何东西。在运行期,它们没有做任何事情。它们没有产生需要执行的代码,一byte都没有。
* std::forward()不仅可以保持左值或者右值不变,同时还可以保持const、Lreference、Rreference、validate等属性不变;
```
#include
#include
#include
#include
using namespace std;
struct A
{
A(int&& n)
{
cout << "rvalue overload, n=" << n << endl;
}
A(int& n)
{
cout << "lvalue overload, n=" << n << endl;
}
};
class B
{
public:
template
B(T1 && t1, T2 && t2, T3 && t3) :
a1_(std::forward(t1)),
a2_(std::forward(t2)),
a3_(std::forward(t3))
{
}
private:
A a1_, a2_, a3_;
};
template
std::unique_ptr make_unique1(U&& u)
{
//return std::unique_ptr(new T(std::forward(u)));
return std::unique_ptr(new T(std::move(u)));
}
template
std::unique_ptr make_unique(U&&... u)
{
//return std::unique_ptr(new T(std::forward(u)...));
return std::unique_ptr(new T(std::move(u)...));
}
int main()
{
auto p1 = make_unique1(2);
int i = 10;
auto p2 = make_unique1(i);
int j = 100;
auto p3 = make_unique(i, 2, j);
return 0;
}
```
# C++中的万能引用和完美转发
1. 阅读这篇博文需要了解C++中的左值(lvalue)和右值(rvalue)的概念,详情参见我的另外一篇博文:[C++移动语义及拷贝优化](https://theonegis.github.io/cxx/C-%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89%E5%8F%8A%E6%8B%B7%E8%B4%9D%E4%BC%98%E5%8C%96/)
2. 万能引用和完美转发多涉及到模板的使用,如若不是自己写模板,则可不用关心
## 万能引用(Universal Reference)
首先,我们来看一个例子:
~~~javascript
#include
using std::cout;
using std::endl;
template
void func(T& param) {
cout << param << endl;
}
int main() {
int num = 2019;
func(num);
return 0;
}
~~~
这样例子的编译输出都没有什么问题,但是如果我们修改成下面的调用方式呢?
~~~javascript
int main() {
func(2019);
return 0;
}
~~~
则会得到一个大大的编译错误。因为上面的模板函数只能接受左值或者左值引用(左值一般是有名字的变量,可以取到地址的),我们当然可以重载一个接受右值的模板函数,如下也可以达到效果。
~~~javascript
template
void func(T& param) {
cout << "传入的是左值" << endl;
}
template
void func(T&& param) {
cout << "传入的是右值" << endl;
}
int main() {
int num = 2019;
func(num);
func(2019);
return 0;
}
~~~
输出结果:
~~~javascript
传入的是左值
传入的是右值
~~~
第一次函数调用的是左值得版本,第二次函数调用的是右值版本。但是,有没有办法只写一个模板函数即可以接收左值又可以接收右值呢?
C++ 11中有万能引用(Universal Reference)的概念:使用`T&&`类型的形参既能绑定右值,又能绑定左值。
但是注意了:**只有发生类型推导的时候,T&&才表示万能引用**;否则,表示右值引用。
所以,上面的案例我们可以修改为:
~~~javascript
template
void func(T&& param) {
cout << param << endl;
}
int main() {
int num = 2019;
func(num);
func(2019);
return 0;
}
~~~
## 引用折叠(Universal Collapse)
万能引用说完了,接着来聊引用折叠(Univers Collapse),因为完美转发(Perfect Forwarding)的概念涉及引用折叠。一个模板函数,根据定义的形参和传入的实参的类型,我们可以有下面四中组合:
* 左值-左值 T& & # 函数定义的形参类型是左值引用,传入的实参是左值引用
* 左值-右值 T& && # 函数定义的形参类型是左值引用,传入的实参是右值引用
* 右值-左值 T&& & # 函数定义的形参类型是右值引用,传入的实参是左值引用
* 右值-右值 T&& && # 函数定义的形参类型是右值引用,传入的实参是右值引用
但是C++中不允许对引用再进行引用,对于上述情况的处理有如下的规则:
所有的折叠引用最终都代表一个引用,要么是左值引用,要么是右值引用。规则是:**如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用**。
即就是前面三种情况代表的都是左值引用,而第四种代表的右值引用。
## 完美转发(Perfect Forwarding)
下面接着说完美转发(Perfect Forwarding),首先,看一个例子:
~~~javascript
#include
using std::cout;
using std::endl;
template
void func(T& param) {
cout << "传入的是左值" << endl;
}
template
void func(T&& param) {
cout << "传入的是右值" << endl;
}
template
void warp(T&& param) {
func(param);
}
int main() {
int num = 2019;
warp(num);
warp(2019);
return 0;
}
~~~
猜一下,上面的输出结果是什么?
~~~javascript
传入的是左值
传入的是左值
~~~
是不是和我们预期的不一样,下面我们来分析一下原因:
`warp()`函数本身的形参是一个万能引用,即可以接受左值又可以接受右值;第一个`warp()`函数调用实参是左值,所以,`warp()`函数中调用`func()`中传入的参数也应该是左值;第二个`warp()`函数调用实参是右值,根据上面所说的引用折叠规则,warp()`函数接收的参数类型是右值引用,那么为什么却调用了调用`func()的左值版本了呢?这是因为在`warp()`函数内部,左值引用类型变为了右值,因为参数有了名称,我们也通过变量名取得变量地址。
那么问题来了,怎么保持函数调用过程中,变量类型的不变呢?这就是我们所谓的“完美转发”技术,在C++11中通过`std::forward()`函数来实现。我们修改我们的`warp()`函数如下:
~~~javascript
template
void warp(T&& param) {
func(std::forward(param));
}
~~~
则可以输出预期的结果。