運算子多載基礎規則
- **不可多載**:
. .* :: ?: sizeof typeid alignof
- **優先級、結合性、操作數個數**與內建型別完全一致,**不可改變**。
- **形式**:
返回型別 operator 運算子(參數列表),至少一個操作數為使用者定義型別。
- **兩種實現**:成員函數(第一操作數隱式
this)或 友元/非成員函數(所有操作數顯式)。
返回值語意:值 vs 引用 —— 核心抉擇
雙目運算子(+、-、*、/ 等):必須返回值
Test operator+(const Test& rhs) const {
return Test(a + rhs.a); // 返回新物件(右值)
}
// 錯誤示範:
Test& operator+(const Test& rhs) const {
Test temp(a + rhs.a);
return temp; // 返回區域變數引用 → 懸空引用,UB
}
**理由**:雙目運算產生**新物件**,區域變數 temp 函數結束即銷毀,返回引用指向已釋放記憶體。返回值觸發 RVO/移動語意,零開銷。
單目運算子(++、--、*、& 等):視語意決定
| 運算子 | 語意 | 返回 | 範例 |
| 前置 ++/-- | 修改自身,返回修改後的 *this | Test& (左值引用) | Test& operator++() { ++a; return *this; } |
| 後置 ++/-- | 修改自身,返回**修改前的副本** | Test (值) | Test operator++(int) { Test old=*this; ++a; return old; } |
| 解引用 * | 返回被指物件 | T& | 智慧指標、迭代器 |
| 取址 & | 返回指標 | T* | 罕見重載,極度謹慎 |
前置遞增返回引用的關鍵作用:鏈式調用
Test c;
++(++c); // 合法:內層 ++c 返回 c 的左值引用,外層繼續修改 c
c++; // 返回右值(舊副本),不可作為左值
// (c++)++; // 編譯錯誤:後置返回值不可修改
若前置 operator++() 返回值而非引用:
Test operator++() { ++a; return *this; } // 返回值 → 產生臨時副本
++(++c); // 內層返回臨時副本,外層修改副本,c 僅增加 1 次!
成員函數 vs 友元函數選型指南
| 場景 | 推薦 | 理由 |
| 單目運算子 (++, --, *, &, ~, !) | 成員函數 | 自然綁定 this,語意清晰 |
| 雙目對稱運算子 (+, -, ==, <, <<, >>) | 友元/非成員 | 支援隱式轉換雙向(obj + 1 / 1 + obj) |
| 賦值 (=)、下標 [], 呼叫 (), 成員存取 -> | **必須**為成員 | 語言規定 |
| 型別轉換運算子 | 成員函數 | 語言規定 |
| 需存取 private 且非對稱 | 友元 | 打破封裝的受控方式 |
完整範例:Test 類別多載全家桶
class Test {
int a;
public:
Test(int v = 0) : a(v) {}
int get() const { return a; }
// 雙目:友元,返回值
friend Test operator+(const Test& lhs, const Test& rhs) {
return Test(lhs.a + rhs.a);
}
friend Test operator-(const Test& lhs, const Test& rhs) {
return Test(lhs.a - rhs.a);
}
// 前置遞增:成員,返回引用
Test& operator++() { ++a; return *this; }
// 後置遞增:成員,返回值,int 佔位參數
Test operator++(int) { Test old = *this; ++a; return old; }
// 輸出流:友元,返回 ostream&
friend std::ostream& operator<<(std::ostream& os, const Test& t) {
return os << t.a;
}
};
常見錯誤總結表
| 錯誤代碼 | 問題 | 修正 |
Test& operator+(Test&) | 雙目運算返回區域引用 | 返回 Test (值) |
Test operator++() (前置) | 返回值導致鏈式調用失效 | 返回 Test& |
Test& operator++(int) (後置) | 返回引用指向區域變數 | 返回 Test (值) |
operator+(int, Test) 成員函數 | 第一參數必須是 this,無法接收 int | 改為友元或利用隱式轉換 |
重載 &&、||、, | 失去短路求值特性,改變語意 | **嚴禁**重載這三個運算子 |
現代 C++ 建議
- **算術/比較運算子**優先寫為 **非成員友元**,配合隱式轉換建構函數,獲得對稱性與擴展性。
- **前置遞增/遞減** 返回
T&,**後置** 返回 T —— 這是標準庫迭代器、智慧指標的統一約定。
- **輸出流
operator<<** 必須為非成員友元,返回 std::ostream& 支援鏈式輸出。
- **三路比較
<=> (C++20)**:單一定義自動產生 ==、!=、<、>、<=、>=,極大減少樣板代碼。
- **避免**重載
& (取址)、, (逗號)、&&/|| (邏輯運算),除非極度確定語意安全。
總結
- 返回值語意由**運算子類別**決定:雙目產生新值 → 返回值;前置修改自身 → 返回引用;後置返回舊副本 → 返回值。
- 成員 vs 友元選擇原則:**對稱雙目用友元**,其餘用成員;賦值/下標/呼叫/轉換**必須**是成員。
- 前置遞增**必須返回引用**以支援鏈式調用與標準演算法要求;後置返回值避免懸空引用。
- 現代 C++ 以
<=> 統一比較運算,以 explicit 建構函數控制隱式轉換,以 RVO/移動語意保證返回值零開銷。
- 上一篇文章: C++ 運算子多載簡化術:利用建構函數隱式轉換與無名物件消除重複代碼
- 下一篇文章: API Hook 核心原理與實戰實現:從消息鉤子到 IAT 修改的完整指南
Copyright ©
程式碼之家 All Rights Reserved