協助完成返回值的優化

2021-07-24 16:39:25 字數 3297 閱讀 8785

乙個返回物件的函式很難有較高的效率, 因為傳值返回會導致呼叫物件內的構造和析構函式,這種呼叫是不能避免的。問題很簡單:乙個函式要麼為了保證正確的行為而返回物件要麼就不這麼做。如果它返回了物件,就沒有辦法擺脫被返回的物件。就說到這。 

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

class rational ; 

// 有關為什麼返回值是const的解釋 

const rational operator*(const rational& lhs, 

const rational& rhs); 

甚至不用看operator*的**,我們就知道它肯定要返回乙個物件,因為它返回的是兩

個任意數字的計算結果。這些結果是任意的數字。operator*如何能避免建立新物件來容納它們的計算結果呢?這是不可能的,所以它必須得建立新物件並返回它。不過c++程式設計師仍然花費大量的精力尋找傳說中的方法,能夠去除傳值返回的物件

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

// 一種不合理的避免返回物件的方法 

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) 

這個函式返回的引用,其指向的物件已經不存在了。它返回的是乙個指向區域性物件

result的引用, 當operator* 退出時result被自動釋放。 返回指向已被釋放的物件的引用,這樣的引用絕對不能使用。 

相信我:一些函式(operator*也在其中)必須要返回物件。這就是它們的執行方法。

不要與其對抗,你不會贏的。 你消除傳值返回的物件的努力不會獲得勝利。 這是一場錯誤的戰爭。 從效率的觀點來看,你不應該關心函式返回的物件,你僅僅應該關心物件的開銷。你所應該關心的是把你的努力引導到尋找減少返回物件的開銷上來,而不是去消除物件本身(我們現在認識到這種尋求是無用的) 。如果沒有與這些物件相關的開銷,誰還會關心有多少物件被建立呢? 以某種方法返回物件, 能讓編譯器消除臨時物件的開銷, 這樣編寫函式通常是很普遍的。

這種技巧是返回constructor argument而不是直接返回物件,你可以這樣做: 

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

// 返回物件的函式 

const rational operator*(const rational& lhs, 

const rational& rhs) 

仔細觀察被返回的表示式。它看上去好象正在呼叫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是命名物件,命名物件不能被消除 。

不過你還可以通過把函式宣告為inline來消除operator*

的呼叫開銷 : 

// the most efficient way to write a function returning 

// an object 

inline const rational operator*(const rational& lhs, 

const rational& rhs)  

「好,不錯」 ,你嘀咕地說, 「優化,誰關心編譯器能做什麼?我想知道它們確實做了什

麼,does any of this nonsense work with real compilers?」 it does。這種特殊的優

化――通過使用函式的return 位置(或者在函式被呼叫位置用乙個物件來替代)來消除區域性臨時物件――是眾所周知的和被普遍實現的。它甚至還有乙個名字:返回值優化(return value optimization)(在《深度探索c++物件模型》中有更多更詳細的講述,它叫之為named return value optimization。但注意,這種優化對普通的賦值運算無效,編譯器不能夠用拷貝建構函式取代賦值運算動作,最終結論是:在確保語意正確的前題下沒有更好的優化可能了) 。實際上這種優化有自己的名字本身就可以解釋為什麼它被廣泛地使用。

20 協助完成「返回值優化(RVO)」

19 最後曾提到了在函式通過傳值方式 by value 返回乙個物件時,不可避免地要生成乙個臨時物件,這會嚴重影響到程式的效率,如下例計算兩個分式的乘積 class crational int numer const get numerator int denom const get denomin...

效率 條款20 協助完成「返回值優化(RVO)」

函式如果返回物件,對效率狂而言是乙個嚴重的挫折,因為以by value方式返回物件,背後隱藏的constructor和destructor都將無法消除。有的人會返回指標,於是導致下列這種拙劣的語法形式 const rational operator const rational lhs,const ...

返回值優化

通過傳值方式返回要建立新物件時,應注意使用的形式,例如在operator return integer left.l right.l 咋看起來這像是乙個 對乙個建構函式的呼叫 其實並非如此。這是臨時物件語法,它是在說 建立乙個臨時integer物件並返回它 據此我們可能認為如果建立乙個有名字的區域性...