文档章节

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

雅各宾
 雅各宾
发布于 2013/11/27 16:05
字数 2231
阅读 231
收藏 5

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

    左值与右值

左值与右值的概念要追溯到 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 之移动语义和传导模板如何实现》。

本文转载自:http://blog.csdn.net/hikaliv/article/details/4541429

雅各宾
粉丝 9
博文 126
码字总数 53538
作品 0
深圳
技术主管
私信 提问
加载中

评论(2)

g
genliu777
清爽怡人。好文字l
雅各宾
雅各宾 博主
所转本文,最提神的一句话是“左值与右值是指表达式的属性,而非对像的属性”,点睛!
如何理解c++的左值引用与右值引用

前几天看了一篇文章《4行代码看看右值引用》 觉得写得不错,但是觉得右值引用的内容还有很多可以去挖掘学习,所以总结了一下,希望能对右值引用有一个更加深层次的认识 一、几个基本概念 1....

刺客五六柒
03/09
0
0
C++11 标准新特性: 右值引用与转移语义

C++11 标准新特性: 右值引用与转移语义 C++ 的新标准 C++11 已经发布一段时间了。本文介绍了新标准中的一个特性,右值引用和转移语义。这个特性能够使代码更加简洁高效。 新特性的目的 右值引...

雅各宾
2014/01/17
74
0
看完这个你还不理解右值引用和移动构造 你就可以来咬我(上)

C++ 右值引用 & 新特性 C++ 11中引入的一个非常重要的概念就是右值引用。理解右值引用是学习“移动语义”(move semantics)的基础。而要理解右值引用,就必须先区分左值与右值。 对左值和右...

天王盖地虎626
09/25
22
0
C++雾中风景10:聊聊左值,纯右值与将亡值

C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度。为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探...

HappenLee
2018/07/19
0
0
VS2010、VS2012、VS2013、VS2015对C++11的支持进度

支持 C++11/14/17 功能(现代 C++) 若要了解有关 Visual Studio 2017 RC 的最新文档,请参阅 Visual Studio 2017 RC 文档。 本文描述了 Visual C++ 中的 C++11/14/17 功能。 本文内容 C++1...

shzwork
05/31
86
0

没有更多内容

加载失败,请刷新页面

加载更多

ArrayList 源码分析

一、概述 本文基于 JDK8 ArrayList 底层通过动态数组的数据结构实现 内存需要连续的空间保证 添加操作涉及到数组的动态扩容 添加,删除都涉及到位置移动操作 随机查找效率快(下标查找) Ar...

hncboy
今天
4
0
采购单品汇总_华南.xlsx

import pandas as pdimport matplotlib.pyplot as pltimport matplotlib as mp1mp1.rcParams["font.family"] = "STFangsong"# 加载《销售》表数据df1 = pd.read_excel(r"C:\Us......

龙玉滕
今天
5
0
OSChina 周五乱弹 —— 一次四千 要4次还能多给一千

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @这次装个文艺青年吧 :#今日歌曲推荐# 分享金志文的单曲《远走高飞》: 版权又回来了现在听歌得好几个软件 《远走高飞》- 金志文 手机党少年们...

小小编辑
今天
11
0
Spring Cloud Alibaba 实战(十) - Spring Cloud GateWay

> 本文主要内容是:为什么要使用网关,整合Gateway,Gateway核心学习:Route,Predicate,Filter,最后使用Gateway聚合微服务请求 先总结至此的架构 1 网关的价值 不使用网关行嘛? 各个请求直接打在...

JavaEdge
今天
4
0
【CKB.DEV 茶话会】第二期:聊聊 CKB 钱包和 Nervos DAO 全流程

CKB.DEV 茶话会第二期:聊聊 CKB 钱包和 Nervos DAO 全流程 为了鼓励更多优秀的开发者和研究人员参与到 CKB 的开发和生态建设中去,我们希望组织一系列 CKB Developer Seminar(CKB.DEV 茶话...

NervosCommunity
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部