作者cppOrz (cppOrz)
看板C_and_CPP
標題[心得] operator+ 與 operator+= 的設計
時間Tue Aug 23 11:30:26 2005
我換了一個新的帳號: gocpp -> cppOrz
因為新的帳號實在是太可愛了!
這篇的內容是有關 operator overloading 設計的觀念
假如有個 class Point 如下:
struct Point
{
Point(int a=0, int b=0) : x(a), y(b) {}
int x, y;
};
現在想替它寫個 operator+ 運算子,該怎麼辦呢?
Point const Point::operator+(Point const &rhs) // 版本 A0
{
return Point(x+rhs.x, y+rhs.y);
}
版本 A0 是常見的作法,但是它其實不太好,有兩個主要的缺點。
一個是介面設計上的,一個是實作方法上的。首先看介面設計的
問題,把 operator+ 當作成員函式的話,+ 號的左邊就不能隱式
轉型,也就是如果某個型別 T 可以隱式轉型成 Point 的話,那
就會出現下面的狀況:
Point p1;
T p2;
Point p;
p = p1 + p2; // ok, 沒問題
p = p2 + p1; // 喔噢,編譯不過。
通常的情況, 加法運算應該是要滿足交換率的。至少 C/C++ 的內建
型別無不如此。但這種設計卻不符合需求,因此必須改良一下。
Point const operator+(Point const &lhs, Point const &rhs) // 版本 A1
{
return Point(lhs.x+rhs.x, lhs.y+rhs.y);
}
把 operator+ 這種二元運算,設為 namespace 層級(而非 class
成員)的函式,是 C++ 函式介面設計的基本觀念,大家如果有注意到
的話,看一下 std::complex<T> 的介面,它就是這樣設計的。這樣不
論左邊或右邊兩個參數都允許隱式轉型。
我印象中 C# 的 operator overloading 介面,就和 C++ 不同,例如
它的 operator+,這種二元運算,仍是設計為成員函式,而且還會幫用
戶自動產生相關的運算,產生規則和實作的方式也和 C++ 標準的做法
不同。
版本 A1 的介面雖然理想了,但它的實作方式卻仍然存在缺陷。原因是,
如果我們想要替 class Point 增加一個 operator+= 的成員,那該怎
麼辦呢?
Point &Point::operator+=(Point const &rhs) // 版本 B1
{
return *this = *this + rhs; // 利用版本 A1
}
初看起來它很完美,沒有什麼問題,但實際上這種設計在 C++ 中可能會
造成效率的損失。原因在於 operator+= 回傳的是 reference to *this,
但其中的過程卻是呼叫了 operator+,而 operator+ 回傳的是物件,也
就是經過了一次不必要的物件複製的動作。(我印象中,C# 自動生成的
程式碼就是類似這樣做的,不過不同的是,C# 有內建 GC,回傳值可以用
new的,不必擔心物件複製的問題。)因為本來 operator+= 完全是不需
要物件複製的:
Point &Point::operator+=(Point const &rhs) // 版本 B2
{
x += rhs.x;
y += rhs.y;
return *this;
}
可以看出,版本 B1 的實作,多了一次不必要的物件複製,而且由於
它是將結果存到 *this 中,RVO(回傳值優化)的編譯器技術也無用
武之地。像 class Point 這樣的小東西,多產生一個暫時物件的差
別幾乎可以視而不見,但若是某些大型架構,例如多維的 Matrix
運算,而且是大量的頻繁使用時,每次多一個暫時物件,整體效率的
損失就很可觀了。
因此,正確的實作法應該是以 operator+= 來實現 operator+
Point const operator+(Point const &lhs, Point const &rhs) // 版本 A2
{
return Point(lhs) += rhs; // 利用版本 B2
}
另外,以上的例子,也告訴我們一件事,就是以下兩種寫法:
Point a, b, c, d;
Point p1 = a + b + c + d;
Point p2 = a;
p2 += b;
p2 += c;
p2 += d;
使用 p2 的方式,會比 p1 來得有效率,所以如果有嚴格的效率
要求時,最好多用 p2 的寫法。
兩個重點:
一、關於介面,不要把 operator+ 設計為成員函式,它應該是個
namespace 函式
二、關於實作,請以 operator+= 來實現 operator+(不要倒過來),
因為後者多了一次物件複製。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.120.214.120
推 renderer:推 Orz大實在太熱心了 61.222.148.171 08/23
→ renderer:另外 cppOrz 唸起來真是帥呀 XD 61.222.148.171 08/23
→ renderer:cppOrz 看起來也是很可愛 XD 61.222.148.171 08/23
→ renderer:真有你的 呵呵 61.222.148.171 08/23
推 FRAXIS:A0版本最好可以加上const 不然常數不能加 140.119.162.51 08/23
→ FRAXIS:return by value 不一定需要加上const 140.119.162.51 08/23
推 khoguan:改取了這種id,舒緩了您嚴謹深入的高人風貌 :-)220.130.208.168 08/23
推 drkkimo:@@? 218.164.26.249 08/23
推 drkkimo:我覺得分析的很詳細 多幾篇這類的探討真不錯 218.164.26.249 08/23
推 sekya:好文 必推一下 59.104.35.45 08/25