More Effective C++ 22:考慮用運算符的賦值形式取代其單獨形式

大多數程序員認爲如果他們能這樣寫代碼:

x = x + y; x = x - y;

那他們也能這樣寫:

x += y; x -= y;

但是,如果 x 和 y 是用戶定義的類型,就不能確保這樣。

operator+operator=operator+=之間沒有任何關係,因此如果你想讓這三個 operator同時存在並具有你所期望的關係,就必須自己實現它們。
同理,operator -, *, /, 等等也一樣。

確保 operator 的賦值形式(比如operator+=)與一個operator的單獨形式(比如operator+)之間存在正常的關係,一種好方法是後者根據前者來實現
比如:

class Rational 
{ 
public: 
	... 
	Rational& operator+=(const Rational& rhs); 
	Rational& operator-=(const Rational& rhs); 
};
const Rational operator+(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs) += rhs;
}

const Rational operator-(const Rational& lhs, const Rational& rhs) 
{ 
	return Rational(lhs) -= rhs; 
}

在這個例子裏,從零開始實現operator+=-=,而operator+operator- 則是通過調用前述的函數來提供自己的功能。使用這種設計方法,只用維護operator的賦值形式就行了。

如果你不介意把所有的 operator 的單獨形式放在全局域裏,那就可以使用模板來替代單獨形式的函數的編寫:

template<class T> 
const T operator+(const T& lhs, const T& rhs) 
{ 
	return T(lhs) += rhs; 
} 

template<class T> 
const T operator-(const T& lhs, const T& rhs) 
{ 
	return T(lhs) -= rhs; 
} 
...

這樣編寫確實不錯,但是到目前爲止,我們還沒有考慮效率問題,在這裏值得指出的是三個效率方面的問題。

1.總的來說operator的賦值形式比其單獨形式效率更高,因爲單獨形式要返回一個新對象,從而在臨時對象的構造和釋放上有一些開銷。operator 的賦值形式把結果寫到左邊的參數裏,因此不需要生成臨時對象來容納operator 的返回值

2.提供operator的賦值形式的同時也要提供其標準形式,允許類的客戶端在便利與效率上做出折衷選擇。也就是說,客戶端可以決定是這樣編寫:

Rational a, b, c, d, result;
result = a + b + c + d;// 可能用了 3 個臨時對象

還是這樣編寫:

result = a; //不用臨時對象 
result += b; //不用臨時對象 
result += c; //不用臨時對象 
result += d; //不用臨時對象

前者比較容易編寫、debug 和維護,並且在 80%的時間裏它的性能是可以被接受的。後者具有更高的效率,估計這對於彙編語言程序員來說會更直觀一些。通過提供兩種方案,你可以讓客戶端開發人員用更容易閱讀的單獨形式的operator 來開發和debug代碼,同時保留用效率更高的 operator 賦值形式替代單獨形式的權力。

3.涉及到operator單獨形式的實現。再看看operator+ 的實現:

template<class T> 
const T operator+(const T& lhs, const T& rhs) 
{ 
	return T(lhs) += rhs; 
}

表達式 T(lhs)調用了 T 的拷貝構造函數。它建立一個臨時對象,其值與 lhs 一樣。這個臨時對象用來與 rhs 一起調用 operator+= ,操作的結果被從 operator+返回。這個代碼好像不用寫得這麼隱密。這樣寫不是更好麼?

template<class T> 
const T operator+(const T& lhs, const T& rhs) 
{ 
	T result(lhs); // 拷貝 lhs 到 result 中 
	return result += rhs; // rhs 與它相加並返回結果 
}

這個模板幾乎與前面的程序相同,但是它們之間還是存在重要的差別。第二個模板包含一個命名對象,result。這個命名對象意味着不能在 operator+ 裏使用返回值優化。第一種實現方法總可以使用返回值優化,所以編譯器爲其生成優化代碼的可能就會更大。

return T(lhs) += rhs;

比大多數編譯器希望進行的返回值優化更復雜。上面第一個函數實現也有這樣的臨時對象開銷,就象你爲使用命名對象 result 而耗費的開銷一樣。然而未命名的對象在歷史上比命名對象更容易清除,因此當我們面對在命名對象和臨時對象間進行選擇時,用臨時對象更好一些。它使你耗費的開銷不會比命名的對象還多,特別是使用老編譯器時,它的耗費會更少。

總結

operator的賦值形式(operator+=)比單獨形式(operator+)效率更高。
做爲一個庫程序設計者,應該兩者都提供,做爲一個應用程序的開發者,在優先考慮性能時你應該考慮考慮用 operator賦值形式代替單獨形式。

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