2008/12/08 05:19 P.M.
原文:http://blog.csdn.net/IanFang/archive/2008/07/13/2645221.aspx
为了给函数直接返回对象提供支持,很多的编译器的做法是给函数提供一个额外的隐式指针参数,将调用者栈活动记录中的某块内存地址传入,在其上构造返回值的临时对象。例如:
- ZooAnimal f()
-
- {
-
- ZooAnimal one;
-
-
-
- return one;
-
- }
ZooAnimal f() { ZooAnimal one; //... return one; }
会被改写成:
- void f_aux(ZooAnimal& result)
-
- {
-
- ZooAnimal one;
-
-
-
- result.ZooAnimal::ZooAnimal(one);
-
- return;
-
- }
void f_aux(ZooAnimal& result) { ZooAnimal one; //... result.ZooAnimal::ZooAnimal(one); //调用拷贝构造函数 return; }
传入额外的参数result, 将局部变量one拷贝构建于result所指的内存之上并返回。
函数虽得以正确的运行,但仔细想想,先构造局部对象one,进行相关处理,再将之拷贝给result,然后析构掉one,这样步骤实属多余(在你的程序没
有特别的要求的前提下)。为何不省掉构造析构one这步,直接在result上构造对象,用它代替one的角色进行相关处理,然后返回呢?答案是肯定的,
即所谓的NRV(Named Reture Value)优化,在NRV优化之下,f()会被这样改写:
-
-
- void f_aux(ZooAnimal& result)
-
- {
-
- result.ZooAnimal::ZooAnimal();
-
-
-
- return;
-
- }
//实施了NRV优化 void f_aux(ZooAnimal& result) { result.ZooAnimal::ZooAnimal(); //... return; }
如此,局部域中的具名对象one被优化掉,其扮演的角色由result全全替代,其效率显然比没实施NRV优化时的效率要高。
问题是,假如我是编译器,我怎么知道是否要将用户的代码实施NRV优化呢,我是应该对所有的这种代码都实施NRV优化吗?
对于很多现在的主流编译器(如gcc)来说,答案是yes,在默认情况下都会对直接返回对象的函数实施NRV优化。但并不是所有的都这样,早期的 cfront需要一个开关来决定是否应该对代码实行NRV优化,这就是是否有客户(程序员)显式提供的拷贝构造函数:如
果客户没有显示提供拷贝构造函数,那么cfront认为客户对默认的逐位拷贝语义很满意,由于逐位拷贝本身就是很高效的,没必要再对其实施NRV优化;但
如果客户显式提供了拷贝构造函数,这说明客户由于某些原因(例如需要深拷贝等)摆脱了高效的逐位拷贝语义,其拷贝动作开销将增大,所以将应对其实施NRV
优化,其结果就是去掉并不必要的拷贝函数调用。
很多人对此质疑,他们的观点是客户明明显式提供了拷贝构造,cfront编译器却偏偏在这时实施NRV优化将其调用动作去掉,这不是可能改
变客户的初衷吗,你cfront又不知道客户的拷贝构造函数提供的是什么语义,难道不是应该在用户没有显式提供拷贝构造函数的情况下才可以实施NRV优化
吗?
对此的解释是:客户必须正确实现构造函数的语义。如果对于相同的语义却出现了不同的结果,那么是客户的程序本身有问题。
例如:
缺省构造一个ZooAnimal类型的对象zb, 可以这样:
也可以先构造za,再拷贝构建出zb:
- ZooAnimal za;
-
- ZooAnimal zb(za);
ZooAnimal za; ZooAnimal zb(za);
抛开效率,如果这两种构造zb的方法产生了不同的结果,那么可以认为客户的程序本身就是错误的,他必须自己对此负责。