操作符重載(三)

  • 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行輸出和預期不符,原因和邏輯操作符重載一樣,都是由函數參數計算順序的不確定性造成的,也就是說:

  • 逗號操作符重載後,構成的逗號表達式無法嚴格按照從左向右的順序計算各個子表達式
  • 逗號操作符重載後無法完全實現原生語義
  • 因此,在工程中不要重載逗號操作符