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

2016-01-29 12:21 30 1 收藏

泛型编程-转移构造函数(Generic Programming: Move Constructor),泛型编程-转移构造函数(Generic Programming: Move Constructor)

【 tulaoshi.com - C语言心得技巧 】

泛型编程-转移构造函数(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++标准没有定义临时对象,但是它假定临时对象是匿名的,例如函数的返回值。按照我们的更一般化的定义,在函数中定义的命名的栈分配的变量也是临时的。稍后为了便于讨论我们使用这个一般化的定义。

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com)

考虑这个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++保证删除空指针是安全的]

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com)

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

来源:https://www.tulaoshi.com/n/20160129/1485758.html

延伸阅读
什么是泛型 一种类型占位符,或称之为类型参数。我们知道在一个方法中,一个变量的值可以作为参数,但其实这个变量的类型本身也可以作为参数。泛型允许我们在调用的时候再指定这个类型参数是什么。在.net中,泛型能够给我们带来的两个明显好处是类型安全和减少装箱、拆箱。 类型安全和装箱、拆箱 作为一种类型参数,泛型很...
请注重,这一节内容是c++的重点,要非凡注重! 我们先说一下什么是构造函数? !-- frame contents -- !-- /frame contents -- 上一个教程我们简单说了关于类的一些基本内容,对于类对象成员的初始化我们始终是建立成员函数然后手工调用该函数对成员进行赋值的,那么在c++中对于类来说有没有更方便的方式能...
标签: Web开发
◦体积小(v1.2.3 15kb) ◦丰富的DOM选择器(CSS1-3 + XPath) ◦跨浏览器(IE6,FF,Safari,Opera) ◦链式代码 ◦强大的事件、样式支持 ◦强大的AJAX功能 ◦易于扩展,插件丰富 jQuery的构造函数接收四种类型的参数:jQuery(expression,context) jQuery(html) jQuery(elements) jQuery(...
觉得作者写得太好了,不得不收藏一下。 对这个例子的理解: //类型参数不能用基本类型,T和U其实是同一类型。 //每次放新数据都成为新的top,把原来的top往下压一级,通过指针建立链接。 //末端哨兵既是默认构造器创建出的符合end()返回true的节点。 代码如下: //: generics/LinkedStack.java // A stack implemented with an internal l...
标签: Web开发
JQuery优点 ◦体积小(v1.2.3 15kb) ◦丰富的DOM选择器(CSS1-3 + XPath) ◦跨浏览器(IE6,FF,Safari,Opera) ◦链式代码 ◦强大的事件、样式支持 ◦强大的AJAX功能 ◦易于扩展,插件丰富 jQuery的构造函数接收四种类型的参数: jQuery(expression,context) jQuery(html) jQuery(elements) jQue...

经验教程

617

收藏

38
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部