重载操作符 - C++重载'->'符号是怎么实现的
PHPz
PHPz 2017-04-17 13:31:21
0
3
751

例如下面的代码:

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()]?
我的疑问是这是如何实现的,这和我对运算符重载的直接理解有所差异。
*/
PHPz
PHPz

学习是最好的投资!

全部回覆(3)
洪涛

這句程式碼的格式不是類似前導運算子嗎?類似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_作為私有成員名稱。

Peter_Zhu

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

刘奇

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

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板