当前位置: 首页 > news >正文

C++ 0x 之左值与右值、右值引用、移动语义、传导模板

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

    左值与右值

左值与右值的概念要追溯到 C 语言,由 C++ 语言继承了上来。C++ 03 3.10/1 如是说:“Every expression is either an lvalue or an rvalue.”左值与右值是指表达式的属性,而非对像的属性。

左值具名,对应指定内存域,可访问;右值不具名,不对应内存域,不可访问。临时对像是右值。左值可处于等号左边,右值只能放在等号右边。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。

       注意区分 ++x 与 x++。前者是左值表达式,后者是右值表达式。前者修改自身值,并返回自身;后者先创建一个临时对像,并用 x 的值赋之,后将修改 x 的值,最后返回临时对像。

函数的返回值一般情况下是右值,C++ 03 5.2.2/10 如是说:“A function call is an lvalue if and only if the result type is a reference.”比如有 vector<int> v 对像,则 v[0] 即为左值,因为 vector 容器的 [] 算符重载函数的返回值为引用。

       左值与右值均可以声明为 const 和 non-const。

    拼接字串的问题

       上面提到函数返回值一般为右值,也即临时对像。对于内置类型(built-in type)来说,临时对像还是可忍的。但对于容器对像来说就是极大的浪费了。举一个 C++ 98/03 标准下最通俗的例子,拼接字符串

0

图一

图一第 18 行,短短一句,背后动作极其复杂我要把 string 对像和常量字串交替拼接起来,问题重点在于如何重载 + 算符。有如下几点需要考虑:

  1. 过程中分别出现 string 对像与常量字串的加法、string 对像与 string 对像的加法。因此需要重载多种 + 算符函数。(加法自左至右)
  2. 比如 string 对像与常量字串的加法,返回的将是一个新生成的 string 对像,因此必须返回这个对像的复本,是临时对像,是右值。又由于加法是连续运算的,下一个加法的重载函数为了接收这一右值,参数表只得写成传值的形式,也即将此临时对像再复制一次,才可传到函数体内操作。总的来说,就是临时对像由前一个函数体转到另一个函数体,需要深度复制两次
  3. 由第二点可知,仅仅由一个加号过渡到另一个加号,就要产生两个昙花一现、转瞬即逝的临时对像复本。若每个字串都很长,对像都很大,拼接个数又特别多,这要产生多少垃圾?为何不能把前一个函数返回时产生的临时对像不用复制,直接拿给下一个函数用呢?也就是从前一个函数“移动”到后一个函数体中。
  4. 对于第三点,C++ 98/03 不允许这么做。因为语义上不支持。由于缺乏“移动语义”,前一个函数产生的临时对像将在函数体退出时析构,外部要想获得只能使用其复本,本体已经不存在了。

    右值引用和移动语义

       针对上述拼接字串的问题,若说,函数返回时产生的临时对像需要复制出去还情有可原——毕竟人家的作用域到头儿了,本体的确不能传递到外部,只能由复本代劳(这是 C++ 与 C# 最大的不同之一);不过话又说回来,复本都复制出来了,为何传递到下一个函数体内还需要再复制一次呢?C++ 98/03 说得是理直气壮:

       “因为我规定了,右值不但不能取址,连引用都不能取!谁让丫传的是临时对像,是右值,传参只能传值!”

       话说得多气人呐!凭什么连引用都不能取?传值就意味着深度复制。C++ 标准委员会发现了这一问题,决定在 C++ 0x 新标准中补充“右值引用”和“移动语义”。

       移动语义:将对方掏空,实体吸收给我自己。见《测试 VS 2010 对 C++ 0x 标准的谨慎支持》。

       举一个临时对像由一个函数传往另一个函数的例子以说明问题。由例子可见,Sck 函数使用右值引用重载版本,接收 Fck 函数返回的临时对像。而在 Fck 函数返回时,完成了一次 Sb 对像的复制。如图:

1

图二

      关于右值引用和移动语义的更多例子,请参见微软 VC 官方博客:《Rvalue Reference》。

    右值引用重载函数几点

  1. 移动构造重载函数和移动赋值算符(assignment operators:=、^=、+=,etc.)重载函数绝不会隐式声明,必须自己定义。
  2. 默认构造函数会被用户自己显式定义的构造函数压制,包括用户自定义复制构造函数和移动构造函数。因故若用户已自定义复制和移动构造函数,且需要无参构造函数时,也需要自己定义。
  3. 隐式复制构造函数会被用户自己显式定义的复制构造函数覆盖,而不是自定义的移动构造函数。
  4. 隐式复制赋值重载函数会被用户自己显式定义的复制赋值重载函数覆盖,而不是自定义的移动赋值重载函数。

       总之一句话,一个类定义完了,程序员嘛也不管,默认构造函数、默认复制构造函数、默认复制赋值函数,编译器都会自动生成。而移动语义的构造函数和赋值函数,则必须由程序员自己显式定义方可使用。

    操作右值对像实现移动语义

       操作右值对像实现移动语义,须使用 std::move () 方法。无论是对类对像,还是对类对像的成员变量。使用 move 方法需要引用 <utility> 头文件。详见下例:

2

图三

   外围函数向内部函数准确传参的问题

       见如下代码块:

void Outer ( params ) { Inner ( params ); }

       由 Outer 函数接收参数后,要准确无误地传递给 Inner 函数。所谓的准确无误包括 params 的左、右值属性和 const / non-const 属性此也即“参数传导语义”。实现这一语义的目的是 Inner 函数的类型检查信息可以与 Outer 外部互通,因此由 Outer 到 Inner 之间的参数传导不能对参数属性有任何的改变。

       在 C++ 98/03 标准下,我们可以使用左值引用标识参数类型: T& params;但若我往里传常量呢?常量是右值,传不进去。好,那改成 const T& params 好了,这下左右值都可以传了;但若我要在函数体内修改 params 的值呢?……

       《C++ 0x 之 Lambda:贤妻与娇娃,你娶谁当老婆?听 FP 如何点化 C++》里说:C++ 是万能的。别以为 C++ 没辙了,我可以重载啊!一种版本我满足不了你的所有要求,我重载出满足你要求的所有版本的函数就好了呗!

       嗯……C++ 果真贤惠!好,我一个参数表有 64 个参数,你把所有版本都重载去吧!估计得有 2^64 个这么多……

    传导模板:forward<>

       话说 C++ 0x 之前的 C++ 在这方面表现得实在是太糙了,简直没法儿看……我们所期待的完美解决方案是只用一个模板即可处理所有情况,而重载函数再能用也不能这么用。好在 C++ 0x 的 <utility> 头文件中有 forward 模板:

template < typename T > void Outer ( T&& t )
       {
              Inner ( std::forward<T> ( t ) );
       }

       不错,写这么一个就解决了。首先 Outer 函数参数表使用 T&& 类型接收参数。推导过程如下:

  • 若参数 t 为 Type& 型即左值引用,则 T&& 推导为 Type& &&,归化为 Type&,为左值引用。
  • 若参数 t 为 Type&& 型即右值引用,则 T&& 推导为 Type&& &&,归化为 Type&&,为右值引用。
  • 若参数 t 为 const Type&(&&) 型,即常左(右)值引用,则 T&& 推导为 const Type&(&&) &&,归化为 const Type&(&&)。
  • 若参数 t 为值类型,则 T&& 为右值引用,待传值型参数为右值。

       一句话,T&& 模板类型可以保留参数信息

       Outer 使用 T&& 是解释清楚了,那 forward<> 是如何保证由 Outer 到 Inner 的平稳过渡呢?若要知 std::move () 和 std::forward () 是如何实现的,请参见:《C++ 0x 之移动语义和传导模板如何实现》。

转载于:https://my.oschina.net/jacobin/blog/179446

相关文章:

  • Win2008学习(十一),解决Remote App Web访问的证书问题
  • 总结第一波~~~
  • Oracle数据库导出报componet'SET_NO_OUTLINES'must be declared的错
  • 关于java集合类TreeMap的理解(转)
  • 2018.10月iOS优质开源项目
  • 从数据出发
  • 树莓派 安装 刷Android Things 小结
  • mongoose 之ShemaType
  • ES6 的 Symbol 超出你想象的强大
  • poj1062
  • 解决iview多表头动态更改列元素发生的错误
  • 比特币淘金热席卷中国专业“挖矿机”受疯抢
  • Python的变量和常量
  • PHP——自定义比较算法
  • 【转】Python 内置函数 locals() 和globals()
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • Git初体验
  • iOS编译提示和导航提示
  • JAVA SE 6 GC调优笔记
  • Java 多线程编程之:notify 和 wait 用法
  • Java 最常见的 200+ 面试题:面试必备
  • Java读取Properties文件的六种方法
  • Mac转Windows的拯救指南
  • php ci框架整合银盛支付
  • Python学习笔记 字符串拼接
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • SOFAMosn配置模型
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 搭建gitbook 和 访问权限认证
  • 分类模型——Logistics Regression
  • 浮现式设计
  • 简单数学运算程序(不定期更新)
  • 理清楚Vue的结构
  • 如何在 Tornado 中实现 Middleware
  • 实现菜单下拉伸展折叠效果demo
  • 使用权重正则化较少模型过拟合
  • 用Visual Studio开发以太坊智能合约
  • 06-01 点餐小程序前台界面搭建
  • 如何用纯 CSS 创作一个货车 loader
  • 通过调用文摘列表API获取文摘
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #{}和${}的区别?
  • #1015 : KMP算法
  • (4)(4.6) Triducer
  • (js)循环条件满足时终止循环
  • (阿里云万网)-域名注册购买实名流程
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)appium-desktop定位元素原理
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • (转载)利用webkit抓取动态网页和链接
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复