MyMSDN

MyMSDN记录开发新知道

[转]从C++的Return Value Optimization (RVO)到C#的value type

先看一段简单的C++代码:

Type get(int I){
    return Type(i);
} 

Type t = get(1); 

这里, 我们从C++的基本语义看上去, 应该是Type(i) 调用一次拷贝构造函数, 在堆栈中生成一个临时对象;然后,用该对象构造返回对象;然后对这个临时对象调用析构函数;在调用者方, 用返回的临时对象调用拷贝构造函数以初始化对象t, 返回对象的析构函数在这之后, 函数返回之前调用。

所以, Type t = get(i); 应该有三个拷贝构造函数和两个析构函数的调用.

可是, 还有一种说法是, 编译器可能会对这两个临时对象进行优化,最终的优化结果会是只有一次的构造函数。因为很明显地可以看到, 这里我们其实只是要用一个整数构造一个Type对象。

嗯. 似乎很有道理!

那么, 哪一种说法对呢? 没有调查就没有发言权,于是本人用VC++6.0做了实验。 放了些cout<<…..在拷贝构造函数里,观察打印的结果, 结果却是跟我的simple, naïve的预测一致。三个拷贝构造函数, 两个析构函数。

“你个弱智编译器!脑袋进水了吧?”(忘了编译器没脑袋了)“很明显在这个例子里我的两个临时对象都没有用的啊!”

于是,上网, 查资料, google一下吧!

下面是我查到的一些结果:

其实, 这种对值传递的优化的研究, 并不只局限于返回值。对下面这个例子:

void f(T t) { } 
void main(void){ 
    T t1;
    f(t1); 
} 

也有这种考虑。

f(T)是按值传递的。语义上应该做一个复制, 使得函数内部对T的改变不会影响到原来的t1.

但是,因为在调用f(t1)之后, 我们没有再使用t1(除了一个隐含的destructor调用),是否可能把复制优化掉, 直接使用t1呢?这样可以节省掉一个拷贝构造函数和一个析构函数。

可是, 不论是对返回值的优化, 还是对上面这种局部对象的优化,在1995年的C++新标准草案出台前都是为标准所严格限制的 (虽然有些编译器并没有遵行这个标准, 还是支持了这种“优化”)

那么, 这又是为什么呢?

这里面涉及到一个普遍的对side-effect的担忧。

什么又是side-effect呢?

所谓side-effect就是一个函数的调用与否能够对系统的状态造成区别。

int add(int i, int j){ return i+j; }就是没有side-effect的,而

void set(int* p, int I, int v){ p[I]=v; }就是有side-effect的。因为它改变了一个数组元素的值, 而这个数组元素在函数外是可见的。

通常意义上来说, 所有的优化应该在不影响程序的可观察行为的基础上进行的。否则,快则快了, 结果却和所想要的完全不同!

而C++的拷贝构造函数和析构函数又很多都是有side-effect的。如果我们的“优化”去掉了一个有side-effect的拷贝构造函数和一个析构函数, 这个“优化”就有可能改变程序的可观察行为。(注意, 我这里说的是“可能”,因为“负负得正”, 两个有side-effect的函数的调用, 在不考虑并行运行的情况下, 也许反而不会影响程序的可观察行为。不过, 这种塞翁失马的事儿, 编译器就很难判断了)

基于这种忧虑, 1995年以前的标准, 明确禁止对含有side-effect的拷贝构造函数和析构函数的优化。同时, 还有一些对C++扩充的提议, 考虑让程序员自己对类进行允许优化的声明。 程序员可以明确地告诉编译器:不错, 我这个拷贝构造函数, 析构函数是有side-effect, 但你别管, 尽管优化, 出了事有我呢!

哎, side-effect真是一个让人又恨又爱的东西!它使编译器的优化变得困难;加大了程序维护和调试的难度。因此 functional language 把side-effect当作洪水猛兽一样,干脆禁止。但同时,我们又很难离开side-effect. 不说程序员们更习惯于imperative 的编程方法, 象数据库操作,IO操作都天然就是side-effect.

不过,个人还是认为C++标准对“优化”的保守态度是有道理的。无论如何,让“优化”可以潜在地偷偷地改变程序的行为总是让人想起来就不舒服的。

但是, 矛盾是对立统一的。(想当年俺马列可得了八十多分呢)。 对这种aggressive的“优化”的呼声是一浪高过一浪。 以Stan Lippeman为首的一小撮顽固分子对标准的颠覆和和平演变的阴谋从来就没有停止过。 这不?在1996年的一个风雨交加的夜晚, 一个阴险的C++新标准草案出炉了。在这个草案里, 加入了一个名为RVO (Return Value Optimization) 的放宽对优化的限制, 妄图走资本主义道路, 给资本家张目的提案。其具体内容就是说:允许编译器对命名过的局部对象的返回进行优化, 即使拷贝构造函数/析构函数有side-effect也在所不惜。这个提议背后所隐藏的思想就是:为了提高效率, 宁可冒改变程序行为的风险。宁要资本主义的苗, 不要社会主义的草了!

我想, 这样的一个罪大恶极的提案竟会被提交,应该是因为C++的值拷贝的语义的效率实在太“妈妈的”了。 当你写一个 Complex operator+(const Complex& c1, const Complex& c2);的时候, 竟需要调用好几次拷贝构造函数和析构函数!同志们!(沉痛地, 语重心长地)社会主义的生产关系的优越性怎么体现啊?

接下来, 当我想Google C++最新的标准, 看RVO是否被最终采纳时, 却什么也找不到了。 到ANSI的网站上去, 居然要付钱才能DOWNLOAD文档。 “老子在城里下馆子都不付钱, down你几个烂文档还要给钱?!”

故事没有结局, 实在是不爽。 也不知是不是因为标准还没有敲定, 所以VC++6 就没有优化, 还是VC根本就没完全遵守标准。

不过,有一点是肯定的。 当写程序的时候, 最好不要依赖于RVO (有人, 象Stan Lippeman, 又叫它NRV优化)。 因为, 不论对标准的争论是否已经有了结果, 实际上各个编译器的实现仍还是各自为政, 没有统一。 一个叫SCOtt Meyers的家伙(忘了是卖什么的了)就说, 如果你的程序依赖于RVO, 最好去掉这种依赖。也就是说, 不管RVO到底标准不标准, 你还是不能用。 不仅不能用, 还得时刻警惕着RVO可能带来的程序行为上的变化。 (也不知这帮家伙瞎忙了半天到底为啥!)

说到这里, 倒想起了C#里一个困惑了我很久的问题。记得读C#的specification的时候, 非常不解为什么C#不允许给value type 定义析构函数。

这里, 先简略介绍一下C#里的value type (原始数据类型, struct 类型)。

在C#里的value_type就象是值, 永远只能copy, 取值。因此, 它永远是in-place的。如果你把一个value type的数据放在一个对象里,它的生命期就和那个对象相同;如果你声明一个value type 的变量在函数中, 它的生命期就在lexical scope里。

{

  The_ValueType value;

}//value 到这里就死菜了

啊呀呀! 这不正是我们怀念的C++的stack object吗?

在C++里,Auto_ptr, shared_ptr, 容器们, 不都是利用析构函数来管理资源的吗?

C#,Java 虽然利用garbage collection技术来收集无用对象, 使我们不用再担心内存的回收。 但garbage collection并不保证无用对象一定被收集, 并不保证Dispose()函数一定被调用, 更不保证一个对象什么时候被回收。 所以对一些非内存的资源, 象数据库连接, 网络连接, 我们还是希望能有一个类似于smart pointer的东西来帮我们管理啊。(try-finally 虽然可以用, 但因为它影响到lexical scope, 有时用起来不那么方便)

于是, 我对C#的取消value type的析构函数充满了深厚的阶级仇恨。

不过, 现在想来, C#的这种设计一定是惩于C++失败的教训:

   1. value type 没有拷贝构造函数。C#只做缺省copy, 没有side-effect
   2. value type 不准有析构函数。C#有garbage collection, 析构函数的唯一用途只会是做一些side-effect象关闭数据库连接。 所以取消了析构函数, 就取消了value type的side-effect.
   3. 没有了side-effect, 系统可以任意地做优化了

对以下程序:

The_Valuetype get(int I){return The_Valuetype(i);}

The_Valuetype t = get(1);

在C#里我们可以快乐地说:只调用了一次构造函数。 再没有side-effect的沙漠, 再没有难以优化的荒原, smart pointer望而却步, 效率之花处处开遍。 I have a dream, ……

转载自:http://gugu99.itpub.net/post/34143/466008

posted on 2010-04-06 19:42 volnet 阅读(550) 评论(0)  编辑 收藏 引用 所属分类: 知识库(KnowledgeLibrary)C/C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


特殊功能