首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

C+之右值引用

有了左值引用为什么还需要右值引用?

在平时编码过程为了减少数据的拷贝,提高性能,我们一般通过引用的方式来传递参数,例如:

如果在上面的程序中我们将函数中的修饰去掉之后呢?我们发现调用居然无法通过了,这是为什么呢?在C++中带const修饰的引用成为常量左值引用,常量左值引用是可以绑定右值的,如果去掉了const修饰的话,就不是常量左值引用了,就不能绑定右值了,也就编译是失败了,因为在不带const修饰的函数中表明引用a是可以修改的,但是传递进去的10确实不可修改的,这就产生了矛盾,也就无法通过编译了。

面对这种情况,右值引用的作用就发挥出来了。右值引用是 C++11 引入的与 Lambda 表达式齐名的重要特性之一。它的引入解决了 C++ 中大量的历史遗留问题,消除了诸如 std::vector、std::string 之类的额外开销。

什么是右值

那么什么是右值呢?按照我们以往的编程经验,一般认为位于等号左边的值就是左值,位于等号右边的值就是右值,然而真相并不是这样的。

例如在异常的代码中如果认为位于表达式左边的值就是左值,位于表达式右边的值就是右值的话,那么在第一行代码中变量a是左值,在第二行代码中变量a却变成了右值,显然这是矛盾的。那么到底该如何区分左值和右值呢?在C++ Primer中对左值和右值的归纳为:

当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。

大概的意思就是说左值就有内存地址的,存活的生命周期较长的,而右值一般是无法获取到内存地址的,生命周期是短暂的。还是以以上的代码为例子,变量a和变量b都是可以通过取地址符号获取到具体的内存地址的,所以变量a和变量b都是左值,而10,是一个普通的字面量,是不可以通过取地址符号获取到具体的内存地址的,所以10就是右值。但是普通的字面量是右值,这有一个例外就是字符串字面量不是一个右值,比如"hello world",这个字符串,我们是可以通过取地址符号获取到地址的。

右值怎么用

在语法上右值就是在左值的基础上增加了一个符号,也就是使用表示右值引用:

右值引用的特点之一是可以延长右值的生命周期,比如以下程序:

在上面的程序中我们通过右值引用延长了函数返回值的生命周期。延长临时对象生命周期并不是这里右值引用的最终目标,其真实目标应该是减少对象复制,提升程序性能。

main.cpp

以上程序在C++11基础上关闭RVO优化,使用命令进行编译后,执行main可执行文件发现如下输出:

通过对比发现确实使用右值引用比普通的引用减少了一次拷贝。到这里可能会有人说有了RVO优化就行了,还需要右值引用干嘛?其实这仅仅是举例说明右值引用的一个小小的场景而已,其用处远不止于此。

移动构造

对于拷贝构造函数而言形参是一个左值引用,而不能是某些函数返回的临时对象,而且在拷贝构造函数中往往进行的是深复制,即一般不会破坏实参对象。而移动构造函数恰恰相反,它接受的是一个右值,其核心思想是通过转移实参对象的数据以达成构造目标对象的目的,也就是移动构造函数会修改实参对象,一般来说调用了移动构造函数之后,实参对象的相关变量资源就会被转移,原本实参的变量就会被置空,也就是实参就不能再使用了, 因此与其叫做移动构造函数不如叫做窃取构造函数更加的贴切。

那么在什么情况会发生移动构造的调用呢?比如在C++11的STL容器中,会根据具体情况自动调用移动构造函数,比如以下例子:

处理在STL容器中可能会自动调用移动构造函数外,我们也可以手动通过或者类型转换手动地调用移动构造函数,例如针对以上的类A:

注意:和拷贝构造函数对于拷贝赋值运算符一样,移动构造函数也对于这一个移动赋值运算符,因为在移动语义中一般会置空实参的相关变量,所以需要注意在移动赋值运算符避免自己赋值给自己的情况

左值、右值、将亡值

在C++11中因为右值引用的出现,对值的类型进行了更具体的划分,

在上图中,左值和纯右值我们比较好理解,左值正如上面所说的那样,一般有内存地址的,存活的生命周期较长的,就是左值,而像整型1,浮点型1.1这种的无法通过取地址符号获取到具体的内存地址的一般就是右值。令人眼花的是这个将亡值,它既可以代表一个左值,又可以代表一个右值,这是怎么的一回事呢?

万能引用和折叠引用

所谓的万能引用就是既可以引用左值,也可以引用右值的引用。例如:

在上面的注释中我们发现只要发生了类型推导就会是万能引用,在T&&和auto&&的初始化过程中都会发生类型的推导所以它们是万能引用。在这个推导过程中,初始化的源对象如果是一个左值,则目标对象会推导出左值引用;反之如果源对象是一个右值,则会推导出右值引用。

在C++11中有一套引用叠加推导的规则叫做引用折叠,通过这套规则,我们可以推导出万能引用的最终类型是什么,如下图:

从图中可以看出在推导过程中,只有实际类型是一个非引用类型或者右值引用类型时,最后推导出来的才是一个右值引用,其余的推导结果都是左值引用。

完美转发

上面介绍了万能引用,它的一个重要用途就是进行完美转发,所谓完美转发指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数,不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。在C++11使用标准库中的函数就可以试下完美转发:

输出:

在上面的例子中我们发现,函数无论传递进去的左值还是右值最终调用的都是左值的test函数,而函数会根据传递的的实参类型是左值还是右值调用不同的test函数,这就是完美转发的威力所在。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20220330A01NL700?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券