首  页 | 资讯中心 | 网络学院 | 天新汽车 | 天新手机 | 天新游戏 | 软件开发 | 考试认证
品 牌 机 | 笔 记 本 | 服 务 器 | 天新数码 - DC - DV - MP3 - MP4 - GPS - TV | 数字家庭
硬件 DIY - 主板 - CPU - 内 存 - 硬 盘 - 显示器 - 显卡 - 光驱 - 机箱 - 键鼠 - 网络设备
办公设备 | 打 印 机 | 扫 描 仪 | 投 影 仪 | 一 体 机 | 传 真 机 | 路 由 器 | 交 换 机
软件下载 | 驱动下载 | 游戏下载 | 源码下载 | 教程下载 | 站长在线 | 产品中心 | 报价中心
开发首页 | 开发语言 | .Net开发 | Java开发 | Web开发 | 数据库开发 | 移动开发 | 游戏开发 | 企业开发 | 操作系统 | 软件工程
VB VC Delphi PB BCB C++ - ASP.net C# VB.net - J2EE J2SE J2ME EJB - ASP PHP JSP CGI - MSSQL Oracle DB2 MySQL - CodingLife
  开发语言首页 | VB开发 | VC开发 | VFP开发 | Delphi开发 | Power Builder | C++ Builder | C/C++ | 汇编
  您现在的位置:天新网 > 软件开发 > 开发语言 > C/C++
《C++0x漫谈》系列之:右值引用
http://dev.21tx.com 2007年09月07日 刘未鹏

每日文章精萃
.Net:C#编程入门三部曲:第三步 增加响应 Java:ejb内部资参之五
ASP:如何调试ASP服务端的组件技术 PHP:第十四节--命名空间 -- Classes and
JSP:Redhat下安装Tomcat CGI:CGI教学:第二章 动态创建图像
VB:VB游戏写作技巧(1)秀图篇 VC:深入浅出VC++串口编程之DOS的串口编

1 2 下一页

  右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来。从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。从库设计者的角度讲,它给库设计者又带来了一把利器。从库使用者的角度讲,不动一兵一卒便可以获得“免费的”效率提升…

  Move语意

  返回值效率问题——返回值优化((N)RVO)——mojo设施——workaround——问题定义——Move语意——语言支持

  大猴子Howard Hinnant写了一篇挺棒的tutorial(a.k.a. 提案N2027),此外最初的关于rvalue-reference的若干篇提案的可读性也相当强。因此要想了解rvalue-reference的话,或者去看C++标准委员会网站上的系列提案(见文章末尾的参考文献)。或者阅读本文。

  源起

  《大史记》总看过吧?

  故事,素介个样子滴…一天,小嗖风风的吹着,在一个伸手不见黑夜的五指…

  我用const引用来接受参数,却把临时变量一并吞掉了。我用非const引用来接受参数,却把const左值落下了。于是乎,我就在标准的每个角落寻找解决方案,我靠!我被8.5.3打败了!…

  设想这样一段代码(既然大同小异,就直接从Andrei那篇著名的文章里面拿来了):

std::vector<int> v = readFile();

  readFile()的定义是这样的:

std::vector<int> readFile()
{
std::vector<int> retv;
… // fill retv
return retv;
}

  这段代码低效的地方在于那个返回的临时对象。一整个vector得被拷贝一遍,仅仅是为了传递其中的一组int,当v被构造完毕之后,这个临时对象便烟消云散。

  这完全是公然的浪费!

  更糟糕的是,原则上讲,这里有两份浪费。一,retv(retv在readFile()结束之后便烟消云散)。二,返回的临时对象(返回的临时变量在v拷贝构造完毕之后也随即香消玉殒)。不过呢,对于上面的简单代码来说,大部分编译器都已经能够做到优化掉这两个对象,直接把那个retv创建到接受返回值的对象,即v中去。

  实际上,临时对象的效率问题一直是C++中的一个被广为诟病的问题。这个问题是如此的著名,以至于标准不惜牺牲原本简洁的拷贝语意,在标准的12.8节悍然下诏允许优化掉在函数返回过程中产生的拷贝(即便那个拷贝构造函数有副作用也在所不惜!)。这就是所谓的“Copy Elision”。

  为什么(N)RVO((Named) Return Value Optimization)几乎形同虚设

  还是按照Andrei的说法,只要readFile()改成这样:

… readFile()
{
if(/* err condition */) return std::vector<int>();
if(/* yet another err condition */) return std::vector<int>(1, 0);
std::vector<int> retv;
… // fill retv
return retv;
}

  出现这种情况,编译器一般都会乖乖放弃优化。

  但对编译器来说这还不是最郁闷的一种情况,最郁闷的是:

std::vector<int> v;
v = readFile(); // assignment, not copy construction

  这下由拷贝构造,变成了拷贝赋值。眼睛一眨,老母鸡变鸭。编译器只能缴械投降。因为标准只允许在拷贝构造的情况下进行(N)RVO。

  为什么库方案也不是生意经

  C++鬼才Andrei Alexandrescu以对C++标准的深度挖掘和利用著名,早在03年的时候(当时所谓的临时变量效率问题已经在新闻组上闹了好一阵子了,相关的语言级别的解决方案也已经在02年9月份粉墨登场)就在现有标准(C++98)下硬是折腾出了一个能100%解决问题的方案来。

  Andrei把这个框架叫做mojo,就像一层爽身粉一样,把它往现有类上面一洒,嘿嘿…猜怎么着,不,不是“痱子去无踪”:P,是该类型的临时对象效率问题就迎刃而解了!

  Mojo的唯一的问题就是使用方法过于复杂。这个复杂度,很大程度上来源于标准中的一个措辞问题(C++标准就是这样,鬼知道哪个角落的一句话能够带出一个brilliant的解决方案来,同时,鬼知道哪个角落的一句话能够抹杀一个原本简洁的解决方案)。这个问题就是我前面提到过的8.5.3问题,目前已经由core language issue 391解决。

  对于库方案来说,解决问题固然是首要的。但一个侵入性的,外带使用复杂性的方案必然是走不远的。因此虽然大家都不否认mojo是一个天才的方案,但实际使用中难免举步维艰。这也是为什么mojo并没有被工业化的原因。

  为什么改用引用传参也等于痴人说梦

void readFile(vector<int>& v){ … // fill v }

  这当然可以。

  但是如果遇到操作符重载呢?

string operator+(string const& s1, string const& s2);

  而且,就算是对于readFile,原先的返回vector的版本支持

BOOST_FOREACH(int i, readFile()){
… // do sth. with i
}

  改成引用传参后,原本优雅的形式被破坏了,为了进行以上操作不得不引入一个新的名字,这个名字的存在只是为了应付被破坏的形式,一旦foreach操作结束它短暂的生命也随之结束:

vector<int> v;
readFile(v);
BOOST_FOREACH(int I, v){
}

// v becomes useless here

  还有什么问题吗?自己去发现吧。总之,利用引用传参是一个解决方案,但其能力有限,而且,其自身也会带来一些其它问题。终究不是一个优雅的办法。

  问题是什么

  《你的灯亮着吗?》里面漂亮地阐述了定义“问题是什么”的重要性。对于我们面临的临时对象的效率问题,这个问题同样重要。

  简而言之,问题可以描述为:

  C++没有区分copy和move语意。

  什么是move语意?记得auto_ptr吗?auto_ptr在“拷贝”的时候其实并非严格意义上的拷贝。“拷贝”是要保留源对象不变,并基于它复制出一个新的对象出来。但auto_ptr的“拷贝”却会将源对象“掏空”,只留一个空壳——一次资源所有权的转移。

  这就是move。

  Move语意的作用——效率优化

  举个具体的例子,std::string的拷贝构造函数会做两件事情:一,根据源std::string对象的大小分配一段大小适当的缓冲区。二,将源std::string中的字符串拷贝过来。

// just for illustrating the idea, not the actual implementation

string::string(const string& o)
{
this->buffer_ = new buffer[o.length() + 1];
copy(o.begin(), o.end(), buffer_);
}

  但是假设我们知道o是一个临时对象(比如是一个函数的返回值),即o不会再被其它地方用到,o的生命期会在它所处的full expression的结尾结束的话,我们便可以将o里面的资源偷过来:

string::string(temporary string& o)
{
// since o is a temporary, we can safely steal its resources without causing any problem
this->buffer_ = o.buffer_;
o.buffer_ = 0;
}

  这里的temporary是一个捏造的关键字,其作用是使该构造函数区分出临时对象(即只有当参数是一个临时的string对象时,该构造函数才被调用)。

  想想看,如果存在这样一个move constructor(搬移式构造函数)的话,所有源对象为临时对象的拷贝构造行为都可以简化为搬移式(move)构造。对于上面的string例子来说,move和copy construction之间的效率差是节省了一次O(n)的分配操作,一次O(n)的拷贝操作,一次O(1)的析构操作(被拷贝的那个临时对象的析构)。这里的效率提升是显而易见且显著的。

  最后,要实现这一点,只需要我们具有判断左值右值的能力(比如前面设想的那个temporary关键字),从而针对源对象为临时对象的情况进行“偷”资源的行动。

  Move语意的作用——使能(enabling)

  再举一个例子,std::fstream。fstream是不可拷贝的(实际上,所有的标准流对象都是不可拷贝的),因而我们只能通过引用来访问一开始建立的那个流对象。但是,这种办法有一个问题,如果我们要从一个函数中返回一个流对象出来就不行了:

// how do we make this happen?
std::fstream createStream()
{ … }

  当然,你可以用auto_ptr来解决这个问题,但这就使代码非常笨拙且难以维护。

  但如果fstream是moveable的,以上代码就是可行的了。所谓“moveable”即是指(当源对象是临时对象时)在对象拷贝语法之下进行的实际动作是像auto_ptr那样的资源所有权转移:源对象被掏空,所有资源都被转移到目标对象中——好比一次搬家(move)。move操作之后,源对象虽然还有名有姓地存在着,但实际上其“实质”(内部拥有的资源)已经消失了,或者说,源对象从语意上已经消失了。

  对于moveable但并非copyable的fstream对象来说,当发生一次move时(比如在上面的代码中,当一个局部的fstream对象被move出createStream()函数时),不会出现同一对象的两个副本,取而代之的是,move的源对象的身份(Identity)消失了,这个身份由返回的临时fstream对象重新持有。也就是说,fstream的唯一性(不可拷贝性——non-copyable)得到了尊重。

  你可能会问,那么被搬空了的那个源对象如果再被使用的话岂不是会引发问题?没错。这就是为什么我们应该仅当需要且可以去move一个对象的时候去move它,比如在函数的最后一行(return)语句中将一个局部的vector对象move出来(return std::move(v)),由于这是最后一行语句,所以后面v不可能再被用到,对它来说所剩下的操作就是析构,因此被掏空从语意上是完全恰当的。

上一篇: Vista后,C++ Builder 2007托管还是原生?
下一篇: 使用Rational进行C++转换的技巧

1 2 下一页

编辑推荐
相关内容
·几个游戏代码(麻将原代码)
·贪吃蛇,还在完善中
·文件加密一例
·Bresenham高效画线算法
·C++类对象的复制-拷贝构造函数
·深入浅出谈垃圾的回收—Java 堆的管理 (1
·C/C++中字符指针数组及指向指针的指针的含
·温故而知新:C++常用排序算法
·编程入门:浅谈C语言的可变参数
·开发精彩实例:窗体自动隐藏
·Visual C++.NET中的字符串转换方法 (1)
·编程实用篇—C++Builder开发动画DLL
·链表的C语言实现之单链表的插入运算
·对C/C++中多维数组指针的理解
·linux kernel 2.4.5 ipv4 socket层的一点解
·自解密的加密程序的制作
·深入UNIX编程之一
·C++中用vectors改进内存的再分配
·C++ 中重载 + 操作符的正确方法
·利用C++实现的贪吃蛇游戏
最近更新
人气最热
·C++ builder 的文件读写操作总结
·C++的明天——以及明天的明天
·Vista后,C++ Builder 2007托管还是原生?
·使用Rational进行C++转换的技巧
·神话与谬误:争论C++前你应当知道什么
·初学者,你应当如何学习C++以及编程
·内存调试技巧:C 语言最大难点揭秘
·C程序实现汉字内码与GB码
·关于C#静态构造函数的几点说明
·使用c#捕获windows的关机事件
·初学者,你应当如何学习C++以及编程
·内存调试技巧:C 语言最大难点揭秘
·C++的明天——以及明天的明天
·C++语言代码检查工具PC-Lint简介 (1)
·结构体数组的定义和引用
·详细解析C++编写的ATM自动取款机模拟程序
·利用C++实现的贪吃蛇游戏
·VC++串口编程之短信应用开发 (1)
·C++ builder 的文件读写操作总结
·Eclipse3.06 + MinGW3.1配置标准C/C++开发

 
·[硬件]漫步者新款耳机功放产品E-10将进军日本
·[数码]数字迷情 缔造经典的各种“8”系列耳机
·[汽车]华普汽车将全线降价 最大降幅近8000元(
·[开发]用Asp生成条形码
·[资讯]方正集团加投医药行业 新医院32亿北京
·[游戏]"50元1000点"优惠活动倒计时!
·[本本]价格性能品牌 笔记本哪方面最为重要
·[办公]“收音机”VS“面包机”惠普爱普生便携
·[手机]联想手机将植入无线服务进军移动搜索
·[考试]计算机等级考试今日起可电话查询成绩
·[学院]图片合成制作“空中灌篮”超酷动画
·[娱乐]打扮靓丽女生
 

关于我们 | 联系我们 | 广告服务 | 工作机会 | 版权声明 | 欢迎投稿 | 网站地图
Copyright © 2000-2008 , www.21tx.com , All Rights Reserved .
© 晨新科技 版权所有 Created by TXSite.net