条款20:协助完成"返回值优化(RVO)"

来源:互联网 发布:js日期选择器插件 编辑:程序博客网 时间:2024/06/05 03:01

条款20:协助完成"返回值优化(RVO)"

2011-01-11 13:39 侯捷 译 电子工业出版社 我要评论(0) 字号:T | T

综合评级:

想读(2)  在读(0)  已读(2)   品书斋鉴(0)   已有4人发表书评

一键收藏,随时查看,分享好友!

《More Effective C++:35个改善编程与设计的有效方法》本书并没有第2 版,原因是当其出版之时(1996),C++ Standard已经几乎定案,本书即依当时的标准草案而写,其与现今的C++标准规范几乎相同。而且可能变化的几个弹性之处,Meyers也都有所说明与提示。本节为大家介绍条款20:协助完成"返回值优化(RVO)"。

AD:2013云计算架构师峰会精彩课程曝光

条款20:协助完成"返回值优化(RVO)"

函数如果返回对象,对"效率狂"而言是一个严重的挫败,因为以 by-value 方式返回对象,背后隐藏的 constructor 和 destructor(见条款19)都将无法消除。问题很简单:如果为了行为正确而不得不这样,函数可返回一个对象;否则就不要那么做。如果真的决定返回对象,那就没有任何办法可以摆脱"返回一个对象"所会遭遇的命运。

考虑分数(rational numbers)的 operator* 函数:

  1. class Rational {  
  2. public:  
  3.   Rational(int numerator = 0, int denominator = 1);  
  4.   ...  
  5.   int numerator() const;  
  6.   int denominator() const;  
  7. };  
  8.  
  9. // 条款6曾解释为什么此返回值是 const。  
  10. const Rational operator*(const Rational& lhs,  
  11.                               const Rational& rhs); 

甚至不必看 operator* 的函数代码,我们也知道它必须返回一个对象,因为它返回两个任意数--两个任意数哟--的乘积,operator* 如何能够在不产生新对象的情况下放置该乘积呢?不可能,所以它必须产生一个新对象并将它返回。尽管如此,C++ 程序员却像希腊神话中的赫克力斯(Herculus)一样,耗费巨大的努力企图寻找消除"by-value 返回方式"的神奇方法(见条款E23和E31)。

有时候人们会返回指针,于是导致以下这种拙劣的语法形式:

  1. // 一个不合理的做法,为求避免返回对象。  
  2. const Rational* operator*(const Rational& lhs,  
  3.                                const Rational& rhs);  
  4.  
  5. Rational a = 10;  
  6. Rational b(1, 2);  
  7.  
  8. Rational c = *(a * b);            // 这看起来自然吗? 

此外它还引出另一个问题:调用者应该删除此函数返回的指针吗?答案通常是 yes,而那通常会导致资源泄漏(resource leaks)。(译注:因为常被忽略或遗忘。)

另一些程序员可能返回 references,于是导出一个可被接受的语法形式:

  1. // 一个危险(而且不正确)的做法,为求避免返回对象。 
    const Rational& operator*(const Rational& lhs,   
    const Rational& rhs); 
    Rational 
    a = 10; Rational b(1, 2);  Rational 
    c = a * b;        // 看起来很合理自然。 

但是这样的函数却根本无法有正确的行为。常见的做法是:

  1. // 一个危险(而且不正确)的做法,为求避免返回对象。
    const Rational& operator*(const Rational& lhs,  
    const Rational& rhs)
    {   Rational result(lhs.numerator() * rhs.numerator(), 
    lhs.denominator() * 
    rhs.denominator());   return result;  

此函数返回一个 reference,指向一个不再存活的对象。更明确地说,它返回一个 reference,指向局部对象 result,但 result 却在operator* 返回时自动被销毁了。返回一个 reference 却指向一个不再存活的对象,一点用都没有,不是吗?

请相信我:有些函数(例如,operator*)硬是得返回对象。它就是必须如此。别对它宣战,你不会赢的。

也就是说,如果函数一定得以 by-value 方式返回对象,你绝对无法消除之。这是一场错误的战争。从效率的眼光来看,你不应该在乎函数返回了一个对象,你应该在乎的是那个对象的成本几何。你需要做的,是努力找出某种方法以降低被返回对象的成本,而不是想尽办法消除对象本身(我们现在知道,那必然徒劳无功)。如果这样的对象不需要什么成本,谁在乎产生多少个呢?

我们可以用某种特殊写法来撰写函数,使它在返回对象时,能够让编译器消除临时对象的成本。我们的伎俩是:返回所谓的 constructor arguments 以取代对象。你可以这么做:

  1. // 返回对象:一个有效率而且正确的做法。 const Rational
    operator*(const Rational& lhs,  
  2.                               const Rational& rhs)  
  3. {  
  4.   return Rational(lhs.numerator() * rhs.numerator(),  
  5.                      lhs.denominator() * rhs.denominator());  

请仔细看看被返回的表达式。看起来好像是你调用了一个 Rational constructor,事实上也的确是。通过此表达式,你产生了一个 Rational 临时对象:

  1. Rational(lhs.numerator() * rhs.numerator(),           
    lhs.denominator() * rhs.denominator()); 

而函数复制此临时对象,当做返回值。

以 constructor arguments 取代局部对象,当做返回值,这笔买卖似乎不见得多划算,因为你还是必须为"函数内的临时对象"的构造和析构付出代价,你还是必须为"函数返回对象"的构造和析构付出代价。但是你已经赚到了某些东西。C++ 允许编译器将临时对象优化,使它们不存在。于是如果你这样调用operator*:
Rational a = 10; Rational b(1, 2);

  1. Rational c = a * b;    // 这里调用了 operator*。 

你的编译器得以消除"operator* 内的临时对象"及"被 operator* 返回的临时对象"。它们可以将 return 表达式所定义的对象构造于c 的内存内。如果编译器这么做,你调用 operator* 时的临时对象总成本为 0,也就是说没有任何临时对象需要被产生出来。取而代之的是,你只需付出一个 constructor(用以产生 c)的代价。你无法做得比这更好了,因为 c 是一个命名对象,而命名对象是不能被消除的(见条款22) 。你可以将此函数声明为 inline,以消除调用 operator* 所花费的额外开销(但请先看过条款E33):

  1. // 函数返回一个对象:最有效率的做法。 inline const
    Rational operator*(const Rational& lhs,    
    const Rational& rhs)  
  2. {  
  3.   return Rational(lhs.numerator() * rhs.numerator(),  
  4.                      lhs.denominator() * rhs.denominator());  

"是啊,是啊"你低声抱怨,"优化……反优化……谁在乎编译器能够做些什么呢?我要知道的是它们真正做了些什么。真正的编译器都有做这个无聊的工作吗?"是的,此特殊的优化行为--利用函数的return 点消除一个局部临时对象(并可能用函数调用端的某对象取代)--不但广为人知而且很普遍地被实现出来。它甚至有个专属名称:return value optimization。"拥有专属名称"这一事实足以反映出它是多么被广泛运用。程序员在寻找理想的C++ 编译器时,不妨询问厂商是否支持 return value optimization。如果A厂商有,而B厂商反问"那是什么呀?",A厂商有明显的竞争优势。

【责任编辑:云霞 TEL:(010)68476606】

回书目   上一节   下一节

原创粉丝点击