关于->
运算符的重载问题
先看代码
#include <iostream>
#include <string>
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
cout << "Test(int i)" << endl;
this->i = i;
}
int value()
{
return i;
}
~Test()
{
cout << "~Test()" << endl;
}
};
class Pointer
{
Test* mp;
public:
Pointer(Test* p = NULL)
{
mp = p;
}
Pointer(const Pointer& obj)
{
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
Pointer& operator = (const Pointer& obj)
{
if( this != &obj )
{
delete mp;
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
return *this;
}
Test* operator -> ()
{
return mp;
}
Test& operator * ()
{
return *mp;
}
bool isNull()
{
return (mp == NULL);
}
~Pointer()
{
delete mp;
}
};
int main()
{
Pointer p1 = new Test(5);
cout << p1->value() << endl;
Pointer p2 = p1;
cout << p1.isNull() << endl;
cout << p2->value() << endl;
return 0;
}
这段代码的执行结果为:
Test(int i)
5
1
5
~Test()
上述代码是一个简单的auto_ptr的实现,其中对->
的重载有一个有意思的C++语义,我们看到Test* operator -> ()
返回的是一个 Test *
,这里对p2->value()
的调用直接替换的话,应解释为 Test *value()
,这明显是一个不合法的表达式,那么,为什么这里通过->
可义成功调用Test的成员函数呢,这里就涉及到C++标准对->
运算符的语义解释,下面我们来具体分析。
因为运算符的结合律不同,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()
- 对于
所以会让人有点糊涂为啥函数签名差不多,用法却不同。
这是由 C++ 标准规定的,对于ptr->mem
根据ptr
类型的不同,操作符->
的解释也不同:
- 当
ptr
的类型是内置指针类型时,等价于(*ptr).mem
- 当
ptr
的类型是类时,等价于ptr.operator->()->mem
你会发现这是一个递归的解释,对于ptr->mem
会递归成:
(*(ptr.operator->().operator->().….operator->())).mem
操作符->
是一元的,而操作符.
是二元的。操作符->
最终是通过操作符.
来访问成员的,而.
这个操作符是不允许重载的,只能由编译器实现。
上述代码中的类Pointer
:
cout << p2->value() << endl;
p2->value();
//等价于 p2.operator->()->value();
//等价于 mp->value();
//等价于 (*mp).value();
到这里也算明白了C++编译器对于->
运算符的重载语义的处理了。