关于'->'运算符的重载问题

关于->运算符的重载问题

先看代码

#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++编译器对于->运算符的重载语义的处理了。

参考C++重载’->'符号是怎么实现的

猜你喜欢

转载自blog.csdn.net/zzl_python/article/details/83243353
今日推荐