例如下面的代码:
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()]?
我的疑问是这是如何实现的,这和我对运算符重载的直接理解有所差异。
*/
因為運算子的結合律不同,dereference operator(
*
)是 right associative 的,而 member access operator(.
和->
)是 left associative 的。這是不同符號的結合律不同的例子,C++ 裡符號相同的時候也會有用法不同導致結合律不同的例子。例如:當你重載一個左結合律的操作符時(如
+ - * / ()
等),往往這個操作符是個二元運算符,對於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()
所以會讓人有點糊塗為啥函數簽名差不多,用法卻不同。
這是由 C++ 標準規定的,對於
ptr->mem
根據ptr
類型的不同,操作符->
的解釋也不同:當
ptr
的型別是內建指標型時,等價於(*ptr).mem
當
ptr
的型別是類時,等價於ptr.operator->()->mem
你會發現這是一個遞歸的解釋,對於
ptr->mem
會遞歸成:操作符
->
是一元的,而操作符.
是二元的。操作符->
最終是透過操作符.
來存取成員的,而.
這個操作符是不允許重載的,只能由編譯器實現。舉個例子,用題主定義的類別
StrPtr
:最後一句題外話,C++ 變數名稱不要用底線當作起始,用底線做起始是保留給編譯器使用的。可以使用
m_somemember
或somemember_
作為私有成員名稱。標準規定的寫法,跟回傳值的型別是指標沒有關係。
而且神奇的是,如果返回的不是一個指針而是一個重載了箭頭操作符的類對象,那麼又會調用該類對象的箭頭操作符,直到某個箭頭操作符返回的是一個raw指針,則對該指針取內容並進行成員訪問,這個過程可以無限嵌套,層層遞推。如果編譯器一層層地找下去,沒有找到傳回一個raw指標的箭頭運算子則會報錯,例如傳回了一個沒有重載箭頭運算子的類別對象,或是int之類的值。
如果按照一般的運算子重載邏輯,那麼箭頭運算子應該回傳一個引用,邏輯上好像也沒什麼問題。但如果出現上述的情況,恐怕就會出現
-> -> ... ->
這種東西了。我想這大概是原因之一吧。
希望跟有不同理解的人交流一下。
標準規定,->要麼呼叫返回物件的->,要麼返回指針,要麼報錯。