Effective C++條款20:協助完成返回值優化

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

考慮以下類:

class Rational 
{ 
public: 
	Rational(int numerator = 0, int denominator = 1); 
	... 
	int numerator() const; 
	int denominator() const; 
};
const Rational operator*(const Rational& lhs, const Rational& rhs);

不用看 operator*的代碼,我們就知道它肯定要返回一個對象,因爲它返回的是兩個任意數字的計算結果。這些結果是任意的數字。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++規則允許編譯器優化不出現的臨時對象。因此如果你在如下的環境裏調用 operator*

Rational a = 10; 
Rational b(1, 2); 
Rational c = a * b;

編譯器就會被允許消除在 operator*內的臨時變量和 operator*返回的臨時變量。
能在爲目標 c 分配的內存裏構造 return 表達式定義的對象。如果你的編譯器這樣去做,調用 operator*的臨時對象的開銷就是零:沒有建立臨時對象。你的代價就是調用一個構造函數――建立 c 時調用的構造函數。而且你不能比這做得更好了,因爲 c 是命名對象,命名對象不能被消除。

你還可以通過把函數聲明爲 inline 來消除 operator*的調用開銷。

inline const Rational operator*(const Rational& lhs, 
 const Rational& rhs) 
{ 
	return Rational(lhs.numerator() * rhs.numerator(), 
	lhs.denominator() * rhs.denominator()); 
}

這種特殊的優化――通過使用函數的 return 位置(或者在函數被調用位置用一個對象來替代)來消除局部臨時對象――是衆所周知的和被普遍實現的。它甚至還有一個名字:返回值優化

但注意,這種優化對普通的賦值運算無效,編譯器不能夠用拷貝構造函數取代賦值運算動作。

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