精華區beta C_and_CPP 關於我們 聯絡資訊
我換了一個新的帳號: 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