例如下面的代码:
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()]?
我的疑问是这是如何实现的,这和我对运算符重载的直接理解有所差异。
*/
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_somemember
或somemember_
作為私有成員名稱。
天蓬老师2017-04-17 13:33:21
標準規定的寫法,跟回傳值的型別是指標沒有關係。
而且神奇的是,如果返回的不是一個指針而是一個重載了箭頭操作符的類對象,那麼又會調用該類對象的箭頭操作符,直到某個箭頭操作符返回的是一個raw指針,則對該指針取內容並進行成員訪問,這個過程可以無限嵌套,層層遞推。如果編譯器一層層地找下去,沒有找到傳回一個raw指標的箭頭運算子則會報錯,例如傳回了一個沒有重載箭頭運算子的類別對象,或是int之類的值。
如果按照一般的運算子重載邏輯,那麼箭頭運算子應該回傳一個引用,邏輯上好像也沒什麼問題。但如果出現上述的情況,恐怕就會出現
-> -> ... ->
這種東西了。我想這大概是原因之一吧。
希望跟有不同理解的人交流一下。