首頁  >  問答  >  主體

重载操作符 - C++重载'->'符号是怎么实现的

例如下面的代码:

class StrPtr{
public:
    StrPtr() : _ptr(nullptr){}
    //拷贝构造函数等省略...
    std::string* operator->()
    {
        return _ptr;
    }
private:
    std::string *_ptr;
};
std::string* operator->()
/*
这句代码的格式不是类似于前导运算符吗?类似于std::string operator*(){//...}不正是*ptr?但是重载的->运算符却是ptr->这样使用的,请问是为什么?
而且std::string* operator->()返回的是指针,为什么可以直接在后面访问类成员,[比如说ptr->size()]?
我的疑问是这是如何实现的,这和我对运算符重载的直接理解有所差异。
*/
PHPzPHPz2715 天前702

全部回覆(3)我來回復

  • PHP中文网

    PHP中文网2017-04-17 13:33:21

    這句程式碼的格式不是類似前導運算子嗎?類似std::string operator*(){//...}不正是*ptr?但是重載的->運算子卻是ptr->這樣使用的,請問是為什麼?

    因為運算子的結合律不同,dereference operator(*)是 right associative 的,而 member access operator(.->)是 left associative 的。這是不同符號的結合律不同的例子,C++ 裡符號相同的時候也會有用法不同導致結合律不同的例子。例如:

     operator | left associative            | right associative
              |   lhs op rhs  /  lhs op     |   op rhs
    ----------+-----------------------------+---------------------------
     ++ --    | postfix increment/decrement | prefix increment/decrement
     + -      | binary add/subtract         | unary plus/minus
     *        | binary multiply             | dereference
     &        | bitwise and                 | address-of
     ()       | function call               | type conversion

    當你重載一個左結合律的操作符時(如+ - * / ()等),往往這個操作符是個二元運算符,對於lhs op rhs,就會呼叫lhs.operator op(rhs)operator op(lhs, rhs),我們只需要按照這個函數簽章來重載運算子就行了。

    但是,如果這個運算子是一元運算子怎麼辦?這就要分情況討論了:

    • 如果這個符號有一種以上的用法(例如++--),對於lhs op(左結合)和op rhs(右結合),我們得想個辦法區分不同的用法啊,所以就會出現用於佔位的函數參數(int):

      • 對於lhs op呼叫lhs.operator op(int)

      • 對於op rhs呼叫rhs.operator op()

    • 如果這個符號只有一種用法(例如->),對於lhs op,那就不需要用於佔位的函數參數了,可以直接寫成類似右結合的函數簽名,即

      • 對於lhs op呼叫lhs.operator op()

    所以會讓人有點糊塗為啥函數簽名差不多,用法卻不同。

    而且std::string* operator->()回傳的是指針,為什麼可以直接在後面存取類別成員,[比如說ptr->size()]?

    這是由 C++ 標準規定的,對於ptr->mem根據ptr類型的不同,操作符->的解釋也不同:

    • ptr的型別是內建指標型時,等價於(*ptr).mem

    • ptr的型別是類時,等價於ptr.operator->()->mem

    你會發現這是一個遞歸的解釋,對於ptr->mem會遞歸成:

    (*(ptr.operator->().operator->().….operator->())).mem

    操作符->是一元的,而操作符.是二元的。操作符->最終是透過操作符.來存取成員的,而.這個操作符是不允許重載的,只能由編譯器實現。

    舉個例子,用題主定義的類別StrPtr

    string s = "abc";
    StrPtr ptr(&s);
    string *sp = &s;
    
    ptr->size();
    // 等价于 ptr.operator->()->size();
    // 等价于 _ptr->size();   这跟 sp->size();   不就一样了吗
    // 等价于 (*_ptr).size(); 这跟 (*sp).size(); 不就一样了吗

    最後一句題外話,C++ 變數名稱不要用底線當作起始,用底線做起始是保留給編譯器使用的。可以使用m_somemembersomemember_作為私有成員名稱。

    回覆
    0
  • 天蓬老师

    天蓬老师2017-04-17 13:33:21

    標準規定的寫法,跟回傳值的型別是指標沒有關係。
    而且神奇的是,如果返回的不是一個指針而是一個重載了箭頭操作符的類對象,那麼又會調用該類對象的箭頭操作符,直到某個箭頭操作符返回的是一個raw指針,則對該指針取內容並進行成員訪問,這個過程可以無限嵌套,層層遞推。如果編譯器一層層地找下去,沒有找到傳回一個raw指標的箭頭運算子則會報錯,例如傳回了一個沒有重載箭頭運算子的類別對象,或是int之類的值。
    如果按照一般的運算子重載邏輯,那麼箭頭運算子應該回傳一個引用,邏輯上好像也沒什麼問題。但如果出現上述的情況,恐怕就會出現
    -> -> ... ->
    這種東西了。我想這大概是原因之一吧。
    希望跟有不同理解的人交流一下。

    回覆
    0
  • 怪我咯

    怪我咯2017-04-17 13:33:21

    標準規定,->要麼呼叫返回物件的->,要麼返回指針,要麼報錯。

    回覆
    0
  • 取消回覆