操作符重載(三)
- 2019 年 10 月 3 日
- 筆記
1. 結論
前面兩次筆記都是C++中可以重載且無副作用的操作符,本次筆記比較特殊,主要列出兩個C++語法允許重載、但在工程中不應該(不允許)重載的操作符:
- 邏輯操作符
&&
和||
- 逗號操作符
,
構成的逗號表達式
這兩個操作符在工程中不允許重載的原因是:重載後無法完全實現操作符的原生語義。
2. 邏輯操作符重載
先來回憶一下邏輯操作符的原生語義
- 操作數只有true和false兩種值
- 邏輯表達式不需要完全計算就能確定最終結果(短路法則)
- 最終結果也只能是true或者false
C++語法是允許重載邏輯操作符的,看下面的示例代碼
#include <iostream> #include <string> using namespace std; class Test { int mValue; public: Test(int v) { mValue = v; } int value() const { return mValue; } }; bool operator && (const Test &l, const Test &r) { return l.value() && r.value(); } bool operator || (const Test &l, const Test &r) { return l.value() || r.value(); } Test func(Test i) { cout << "Test func(Test i) : i.value() = " << i.value() << endl; return i; } int main() { Test t0(0); Test t1(1); /* * 根據短路法則,期望的正確輸出為: * Test func(Test i) : i.value() = 0 * Result is false! */ if( func(t0) && func(t1) ) { cout << "Result is true!" << endl; } else { cout << "Result is false!" << endl; } cout << endl; /* * 根據短路法則,期望的正確輸出為: * Test func(Test i) : i.value() = 1 * Result is true! */ if( func(1) || func(0) ) { cout << "Result is true!" << endl; } else { cout << "Result is false!" << endl; } return 0; }
代碼中注釋了我們期待的正確輸出,但實際運行結果一個都沒對上,為什麼呢?
- 操作符重載的本質是通過函數調用擴展操作符的功能,47行和63行的if條件分別等價於
operator && (func(t0), func(t1))
和operator || (func(t1), func(t0))
- 重載函數在進入函數體之前必須完成所有參數的計算,而函數參數的計算順序是不確定的
- 短路法則完全失效,邏輯操作符重載後無法完全實現原生語義
因此,在工程中應當避免重載邏輯操作符,建議採用重載比較操作符或提供成員函數的方式,來代替重載邏輯操作符。
3. 逗號操作符重載
逗號操作符,
可以構成逗號表達式,下面是逗號表達式的原生語義
- 逗號表達式用於將多個子表達式連接為一個表達式,如
exp1, exp2, exp3, ... , expN
- 逗號表達式中前N-1個子表達式可以沒有返回值,最後一個子表達式必須有返回值
- 逗號表達式的最終結果為最後一個子表達式的值
- 逗號表達式嚴格按照從左向右的順序計算每個子表達式的值
#include <iostream> #include <string> using namespace std; void func(int i) { cout << "func() : i = " << i << endl; } int main() { int a[3][3] = { (0, 1, 2), //逗號表達式,等價於a[3][3] = {2, 5, 8,}; (3, 4, 5), (6, 7, 8) }; int i = 0; int j = 0; while (i < 5) func(i), //逗號表達式,等價於func(i), i++; i++; cout << endl; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { cout << a[i][j] << endl; } } cout << endl; (i, j) = 6; //逗號表達式,等價於 j = 6; cout << "i = " << i << endl; cout << "j = " << j << endl; return 0; }
在C++中重載逗號操作符是合法的,重載函數的參數必須有一個是類類型,且重載函數的返回值類型必須是引用
Class &operator , (const Class &a, const Class &b) { return const_cast<Class &>(b); }
看下面的示例代碼
#include <iostream> #include <string> using namespace std; class Test { int mValue; public: Test(int i) { mValue = i; } int value() { return mValue; } }; Test &operator , (const Test &a, const Test &b) { return const_cast<Test &>(b); } Test func(Test &i) { cout << "func() : i = " << i.value() << endl; return i; } int main() { Test t0(0); Test t1(1); /* * 期望的正確輸出為: * func() : i = 0 * func() : i = 1 * 1 */ Test tt = (func(t0), func(t1)); cout << tt.value() << endl; return 0; }
雖然第44行輸出結果和預期相符,但第43行輸出和預期不符,原因和邏輯操作符重載一樣,都是由函數參數計算順序的不確定性造成的,也就是說:
- 逗號操作符重載後,構成的逗號表達式無法嚴格按照從左向右的順序計算各個子表達式
- 逗號操作符重載後無法完全實現原生語義
- 因此,在工程中不要重載逗號操作符