More Effective C++ 條款20

條款20:協助完成返回值優化<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

一個返回對象的函數很難有較高的效率,因爲傳值返回會導致調用對象內的構造和析構函數(參見條款19),這種調用是不能避免的。問題很簡單:一個函數要麼爲了保證正確的行爲而返回對象要麼就不這麼做。如果它返回了對象,就沒有辦法擺脫被返回的對象。就說到這。

考慮rational(有理數)類的成員函數operator*:

class Rational {

public:

  Rational(int numerator = 0, int denominator = 1);

  ...

  int numerator() const;

  int denominator() const;

};

 

// 有關爲什麼返回值是const的解釋,參見條款6,

const Rational operator*(const Rational& lhs,

                         const Rational& rhs);

甚至不用看operator*的代碼,我們就知道它肯定要返回一個對象,因爲它返回的是兩個任意數字的計算結果。這些結果是任意的數字。operator*如何能避免建立新對象來容納它們的計算結果呢?這是不可能的,所以它必須得建立新對象並返回它。不過C++程序員仍然花費大量的精力尋找傳說中的方法,能夠去除傳值返回的對象(參見Effective C++ 條款23和條款31)。

有時人們會返回指針,從而導致這種滑稽的句法:

// 一種不合理的避免返回對象的方法

const Rational * operator*(const Rational& lhs,

                           const Rational& rhs);

 

Rational a = 10;

Rational b(1, 2);

 

Rational c = *(a * b);             //你覺得這樣很“正常”麼?

它也引發出一個問題。調用者應該刪除函數返回對象的指針麼?答案通常是肯定的,並且通常會導致資源泄漏。

其它一些開發人員會返回引用。這種方法能產生可接受的句法,

//一種危險的(和不正確的)方法,用來避免返回對象

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs);

 

Rational a = 10;

Rational b(1, 2);

 

Rational c = a * b;                          // 看上去很合理

但是函數不能被正確地實現。一種嘗試的方法是這樣的:

// 另一種危險的方法 (和不正確的)方法,用來

// 避免返回對象

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs)

{

  Rational result(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

  return result;

}

這個函數返回的引用,其指向的對象已經存在了。它返回的是一個指向局部對象result的引用,當operator* 退出時result被自動釋放。返回指向已被釋放的對象的引用,這樣的引用絕對不能使用。

相信我:一些函數(operator*也在其中)必須要返回對象。這就是它們的運行方法。不要與其對抗,你不會贏的。

你消除傳值返回的對象的努力不會獲得勝利。這是一場錯誤的戰爭。從效率的觀點來看,你不應該關心函數返回的對象,你僅僅應該關心對象的開銷。你所應該關心的是把你的努力引導到尋找減少返回對象的開銷上來,而不是去消除對象本身(我們現在認識到這種尋求是無用的)。如果沒有與這些對象相關的開銷,誰還會關心有多少對象被建立呢?

以某種方法返回對象,能讓編譯器消除臨時對象的開銷,這樣編寫函數通常是很普遍的。這種技巧是返回constructor argument而不是直接返回對象,你可以這樣做:

// 一種高效和正確的方法,用來實現

// 返回對象的函數

const Rational operator*(const Rational& lhs,

                         const Rational& rhs)

{

  return Rational(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

}

仔細觀察被返回的表達式。它看上去好象正在調用Rational的構造函數,實際上確是這樣。你通過這個表達式建立一個臨時的Rational對象,

Rational(lhs.numerator() * rhs.numerator(),

         lhs.denominator() * rhs.denominator());

並且這是一個臨時對象,函數把它拷貝給函數的返回值。

返回constructor argument而不出現局部對象,這種方法還會給你帶來很多開銷,因爲你仍舊必須爲在函數內臨時對象的構造和釋放而付出代價,你仍舊必須爲函數返回對象的構造和釋放而付出代價。但是你已經獲得了好處。C++規則允許編譯器優化不出現的臨時對象(temporary objects out of existence)。因此如果你在如下的環境裏調用operator*

Rational a = 10;

Rational b(1, 2);

 

Rational c = a * b;                          // 在這裏調用operator*

編譯器就會被允許消除在operator*內的臨時變量和operator*返回的臨時變量。它們能在爲目標c分配的內存裏構造return表達式定義的對象。如果你的編譯器這樣去做,調用operator*的臨時對象的開銷就是零:沒有建立臨時對象。你的代價就是調用一個構造函數――建立c時調用的構造函數。而且你不能比這做得更好了,因爲c是命名對象,命名對象不能被消除(參見條款22)。不過你還可以通過把函數聲明爲inline來消除operator*的調用開銷(不過首先參見Effective C++ 條款33):

// the most efficient way to write a function returning

// an object

inline const Rational operator*(const Rational& lhs,

                                const Rational& rhs)

{

  return Rational(lhs.numerator() * rhs.numerator(),

                  lhs.denominator() * rhs.denominator());

}

不錯”,你嘀咕地說,“優化誰關心編譯器能做什麼我想知道它們確實做了什麼Does any of this nonsense work with real compilers? It does。這種特殊的優化――通過使用函數的return location或者用一個在函數調用位置的對象來替代),來消除局部臨時對象――是衆所周知的和被普遍實現的。它甚至還有一個名字:返回值優化(return value optimization)。實際上這種優化有自己的名字本身就可以解釋爲什麼它被廣泛地使用。尋找C++編譯器的程序員會問銷售商編譯器是否有返回值優化功能。如果一個銷售商說有而另一個問“那是什麼東西?”,第一個銷售商就會有明顯的競爭優勢。啊,資本主義,有時你實在應該去愛它。(謹代表作者觀點,譯者堅決擁護四項基本原則    譯者注  :- )

 

 

 

附錄:

文中最後一段黑體部分如何翻譯,我有些拿不準,請高手告知,爲了容易理解,我在此附上此文最後一段的英文原文:

"Yeah, yeah," you mutter, "optimization, schmoptimization. Who cares what compilers can do? I want to know what they do do. Does any of this nonsense work with real compilers?" It does. This particular optimization — eliminating a local temporary by using a function's return location (and possibly replacing that with an object at the function's call site) — is both well-known and commonly implemented. It even has a name: the return value optimization. In fact, the existence of a name for this optimization may explain why it's so widely available. Programmers looking for a C++ compiler can ask vendors whether the return value optimization is implemented. If one vendor says yes and another says "The what?," the first vendor has a notable competitive advantage. Ah, capitalism. Sometimes you just gotta love it.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章