走进C++11(二十一)移动语义、完美转发

图片

 

 

上文讲了什么是右值引用。右值引用(rvalue reference)是 C++11 为了实现移动语意(move semantic)和完美转发(perfect forwarding)而提出来的。

 

右值引用,简单说就是绑定在右值上的引用。右值的内容可以直接移动(move)给左值对象,而不需要进行开销较大的深拷贝(deep copy)。

 

在面向对象中,有的类是可以拷贝的,例如车、房等他们的属性是可以复制的,可以调用拷贝构造函数,有点类的对象则是独一无二的,或者类的资源是独一无二的,比如 IO 、 std::unique_ptr等,他们不可以复制,但是可以把资源交出所有权给新的对象,称为可以移动的。

 

C++11最重要的一个改进之一就是引入了移动语义,这样在一些对象的构造时可以获取到已有的资源(如内存)而不需要通过拷贝,申请新的内存,这样移动而非拷贝将会大幅度提升性能。例如有些右值即将消亡析构,这个时候我们用移动构造函数可以接管他们的资源。

 

C++11用std::move来实现移动语义。下边看一个小栗子,请忽略std::forward:

 

#include <iostream>#include <utility>void reference(int& v) {  std::cout << "左值引用" << std::endl;}void reference(int&& v) {  std::cout << "右值引用" << std::endl;}template <typename T>void pass(T&& v) {  std::cout << "普通传参:";  reference(v);  std::cout << "std::move 传参:";  reference(std::move(v));  std::cout << "std::forward 传参:";  reference(std::forward<T>(v));}int main() {  std::cout << "传递右值:" << std::endl;  pass(1);  std::cout << "传递左值:" << std::endl;  int v = 1;  pass(v);  return 0;}
输出:
传递右值:普通传参:左值引用std::move 传参:右值引用std::forward 传参:右值引用传递左值:普通传参:左值引用std::move 传参:右值引用std::forward 传参:左值引用

 

可能有的小伙伴看出来异样,传递右值时:

  1.   pass函数明明传入一个右值,但是打印出左值引用。

  2. 右值通过std::move就变成了右值引用。

传递左值时:

    1.  但是打印了左值引用。

    2. 左值引用通过std::move就变成了右值引用。

 

这两个问题2都好解释,std::move函数就是为了移动而生。

 

下边说一下传递右值的异常。

 

我们来看这个模板,编译器将T推导为 int& 类型。当我们用 int& 替换掉 T 后,得到 int & &&。看起来两个&&被消除了。

 

但是我们尝试写下边代码的时候却报错了!!!!

 

int & &rra = ra;  // 编译器报错:不允许使用引用的引用!

 

编译器真是双标党,编译器不允许我们自己把代码写成int& &&,它自己却这么干了 =。= 

 

那么 int & &&到底是个什么东西呢?!它就是引用折叠,也有人叫它引用坍缩,C++对于这种类型的折叠有下边的规则:

 

函数形参类型

实参参数类型

推导后函数形参类型

T&

左引用

T&

T&

右引用

T&

T&&

左引用

T&

T&&

右引用

T&&

 

编译器不允许我们写下类似int & &&这样的代码,但是它自己却可以推导出int & &&代码出来。它的理由就是:我(编译器)虽然推导出T为int&,但是我在最终生成的代码中,利用引用折叠规则,将int & &&等价生成了int &。推导出来的int & &&只是过渡阶段,最终版本并不存在。所以也不算破坏规定咯。

 

所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。规则就是:如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。                                                                         《Effective Modern C++》

 

为了解决引用折叠引入的问题,一个新的概念--完美转发就闪亮登场了。

 

还是刚才的小程序,std::forward 传参: 输出的都是我们想要的类型。

 

std::forward的源码形式大致是这样:

 

/* *  精简了标准库的代码,在细节上可能不完全正确,但是足以让我们了解转发函数 forward 的了 */ 
template<typename T>T&& forward(T &param){  return static_cast<T&&>(param);}

 

我们来仔细分析一下这段代码, 可以看到,不管T是值类型,还是左值引用,还是右值引用,T&经过引用折叠,都将是左值引用类型。也就是forward 以左值引用的形式接收参数 param, 然后 通过将param进行强制类型转换 static_cast<T&&> (),最终再以一个 T&&返回,根据上边的引用折叠规则,就能完美的forward传参。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值