当前位置: 首页 > 图文教程 > 开发语言 > VC++ > 泛型编程-转移构造函数(Generic Programming: Move Constructor)

VC++
在类VC的界面实现中加入目录树
软件换肤技术在 BCB 中的实现
利用非模窗口生成MDI介面
报表输出轻松搞定
Windows 中不规则窗体的编程实现
解说Win32的窗口子类化
使用测试优先方法开发用户界面
一个简单的登录对话框的实现
一个简单的日记本程序
从资源中加载皮肤
一个在RichEdit中添加表情图象的类
ActiveSkin 4.3 软件换肤在VC中的实现
一种另类“关于(About)”对话框的动态显示方法
对话框打印预览及打印
关于如何换肤、子类化的解决方案
制作 MSN、QQ 的消息提示窗口
如何对 BCGControlBarPro 进行换肤
定制个性化的对话框窗口类
改变窗口中的光标形状
更新MFC中的视图,跟踪.NET Framework中的事件

VC++ 中的 泛型编程-转移构造函数(Generic Programming: Move Constructor)


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-10-30   浏览: 74 ::
收藏到网摘: n/a

泛型编程-转移构造函数(Generic Programming: Move Constructor)
作者:Andrei Alexandrescu

提交者:eastvc 发布日期:2003-9-20 10:07:17
原文出处:http://www.cuj.com/experts/2102/alexandr.htm


编译:死猫
校对:Wang Tianxing

1 引言

我相信大家很了解,创建、复制和销毁临时对象是C++编译器最爱的户内运动。不幸的是,这些行为会降低C++程序的性能。确实,临时对象通常被视为C++程序低效的第一因素[1]。

下面的代码是正确的:

vector < string > ReadFile();vector < string > vec = ReadFile();

或者

string s1, s2, s3;//...s1 = s2 + s3;

但是,如果关心效率,则需要限制类似代码的使用。ReadFile()和operator+创建的临时对象分别被复制然后再废弃。这是一种浪费!

为了解决这个问题,需要一些不太优雅的约定。例如,可以按照引用传递函数参数:

void ReadFile(vector < string > & dest);vector < string > dest;ReadFile(dest);

这相当令人讨厌。更糟的是,运算符没有这个选择,所以如果想高效的处理大对象,程序员必须限制创建临时对象的运算符的使用:

string s1, s2, s3;//...s1 = s2;s1 += s3;

这种难缠的手法通常减缓了设计大程序的大团队的工作效率,这种强加的持续不断的烦恼扼杀了编写代码的乐趣而且增加了代码数量。难道从函数返回值,使用运算符传递临时对象,这样做是错误的吗?

一个正式的基于语言的解决方案的提议已经递交给了标准化委员会[2]。Usenet上早已引发了大讨论,本文也因此在其中被反复讨论过了。

本文展示了如何解决C++存在的不必要的复制问题的方法。没有百分之百让人满意地解决方案,但是一个干净的程度是可以达到的。让我们一步一步的来创建一个强有力的框架,来帮助我们从程序中消除不需要的临时对象的复制。这个解决方案不是百分之百透明的,但是它消除了所有的不需要的复制,而且封装后足以提供一个可靠的替代品,直到多年以后,一个干净的、基于语言的标准化的实现出现。

2 临时对象和“转移构造函数”(Move Constructor)

在和临时对象斗争了一段时间之后,我们意识到在大多数情况下,完全消除临时对象是不切实际的。大多数时候,关键是消除临时对象的复制而不是临时对象本身。下面详细的讨论一下这个问题。

大多数具有昂贵的复制开销的数据结构将它们的数据以指针或者句柄的形式储存。典型的例子包括,字符串(String)类型储存大小(size)和字符指针(char*),矩阵(Matrix)类型储存一组整数维数和数据存储区指针(double*),文件(File)类型储存一个文件句柄(handle)。

如你所见,复制字符串、矩阵或者文件的开销不是来自于复制实际的数据成员,而是来自于指针或者句柄指向的数据的复制。

因此,对于消除复制的目的来说,检测临时对象是一个好方法。残酷点说就是,既然一个对象死定了,我们完全可以趁着它还新鲜,把它用作器官捐献者。

顺便说一下什么是临时对象?这里给出一个非正式的定义:

当且仅当离开一段上下文(context)时在对象上执行的仅有的操作是析构函数时,一个对象被看成是临时的。这里上下文可能是一个表达式,也可能是一个语句范围,例如函数体。

C++标准没有定义临时对象,但是它假定临时对象是匿名的,例如函数的返回值。按照我们的更一般化的定义,在函数中定义的命名的栈分配的变量也是临时的。稍后为了便于讨论我们使用这个一般化的定义。

考虑这个String类的实现(仅作为示例):

class String{	char* data_;	size_t length_;public:	~String()	{	delete[] data_;	}	String(const String& rhs)	: data_(new char[rhs.length_]), length_(rhs.length_)	{	std::copy(rhs.data_, rhs.data_ + length_, data_);	}	String& operator=(const String&);	//...};

这里复制的成本主要由data_的复制组成,也就是分配新的内存并复制。如果可以探测到rhs实际上是临时的就好了。考虑下面的C++伪代码:

class String{	//...同前...	String(temporary String& rhs)	: data_(rhs.data_), length_(rhs.length_)	{	//复位源字符串使它可以被销毁	//因为临时对象的析构函数仍然要执行	rhs.data_ =0;	}	//...}

这个我们虚构的重载构造函数String(temporary String&)在创建一个String临时对象(按照前面的定义)时调用。然后,这个构造函数执行了一个rhs对象转移的构造过程,只是简单的复制指针而不是复制指针指向的内存块。最后,“转移构造函数”复位源指针rhs.data_(恢复为空指针)。使用这个方法,当临时对象被销毁时,delete[]会无害的应用在空指针上[译注:C++保证删除空指针是安全的]

一个重要的细节是“转移构造”后rhs.length_没有被清0。按照教条主义的观点,这是不正确的,因为data_==0而len