三、成员访问操作符
为了支持指针型类,例如迭代器,C++语言允许重载解引用操作符(*)和箭头操作符(->)。
箭头操作符必须定义为类成员函数。解引用操作符不要求定义为成员,但是将他作为成员一般也是正确的!
1、构建更安全的指针
解引用操作符和箭头操作符常用在实现智能指针的类中。作为例子,假定想要定义一个类类型表示指向第十二章Screen类型对象的指针,将该类命名为ScreenPtr。
不用为 ScreenPtr类定义默认构造函数。因此,我们知道一个ScreenPtr对象将总 是指向一个Screen对象,不会有未绑定的ScreenPtr。应用程序可以使用ScreenPtr对象而无须首先测试它是否指向一个Screen对象。
像HasPtr类一样,ScreenPtr类将对其指针进行使用计数。我们将定义一个伙伴类保存指针及其相关使用计数:
class ScrPtr
{
friend class ScreenPtr;
Screen *sp;
size_t use;
ScrPtr(Screen *p):sp(p),use(1){}
~ScrPtr()
{
delete sp;
}
};
ScrPtr保存指针及其相关使用计数。将ScreenPtr设为友元,以便ScreenPtr可以访问使用计数。
class ScreenPtr
{
public:
ScreenPtr(Screen *p):ptr(new ScrPtr(p)){}
ScreenPtr(const ScreenPtr &orig):ptr(orig.ptr)
{
++ ptr->use;
}
ScreenPtr &operator=(const ScreenPtr &);
~ScreenPtr()
{
if ( -- ptr->use == 0 )
delete ptr;
}
private:
ScrPtr *ptr;
};
因为没有默认构造函数,所以ScreenPtr类型的每个对象都必须提供一个初始化函数,初始化函数必须是另一个ScreenPtr对象或者执行动态分配的Screen指针。
ScreenPtr p1; //Error
ScreenPtr p2(new Screen); //OK
2、支持指针操作
指针支持的基本操作有解引用操作和箭头操作,我们的类可以这样定义这些操作:
class ScreenPtr
{
public:
Screen &operator*()
{
return * ptr->sp;
}
Screen *operator->()
{
return ptr -> sp;
}
const Screen &operator*() const
{
return * ptr -> sp;
}
const Screen *operator->() const
{
return ptr -> sp;
}
//AS Before...
private:
ScrPtr *ptr;
};
3、重载解引用操作符
我们的解引用操作符有const和非 const版本。它们的区别在于返回类型:const成员返回const引用以防止用户改变基础对象。
4、重载箭头操作符
箭头操作符与众不同。它可能表现得像二元操作符一样:接受一个对象和一个成员名。对对象解引用以获取成员。不管外表如何,箭头操作符不接受显式形参。
这里没有第二个形参,因为->的右操作数不是表达式,相反,是对应着类成员的一个标识符。没有明显可行的途径将一个标识符作为形参传递给函数,相反,由编译器处理获取成员的工作。
当这样编写时:
point -> action();
由于优先级规则,它实际等价于这样编写:
(point -> action) ();
换句话说,我们想要调用的是对point->action求值的结果。编译器这样对该代码进行求值【下面的一段话不太好理解,注意】:
1)如果point是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的 action成员。
2)否则,如果action是定义了operator->操作符的类的一个对象,则point-> action 与point.operator->()->action相同。即,执行point的operator-> (),然后使用该结果重复这三步。
3)否则,代码出错。
5、使用重载箭头
可以这样使用ScreenPtr对象访问Screen对象的成员:
Screen myScreen;
ScreenPtr p(&myScreen);
p -> display();
因为 p是一个ScreenPtr对象,p->display的含义与对(p.operator->())->display求值相同。对p.operator->()求值将调用ScreenPtr类的operator->,它返回指向Screen对象的指针,该指针用于获取并运行ScreenPtr所指对象的display成员。
6、对重载箭头的返回值的约束(最后两段不太好理解(ˇ_ˇ)~)
重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象!
如果返回类型是指针,则内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员。
如果返回类型是类类型的其他对象(或是这种对象的引用),则将递归应用该操作符。编译器检查返回对象所属类型是否具有成员箭头,如果有,就应用那个操作符;否则,编译器产生一个错误。这个过程继续下去,直到返回一个指向带有指定成员的的对象的指针,或者返回某些其他值,在后一种情况下,代码出错。
//P446 习题14.20
ScreenPtr &ScreenPtr::operator=(const ScreenPtr &rhs)
{
++ rhs.ptr -> use;
if (-- ptr->use == 0 )
{
delete ptr;
}
ptr = rhs.ptr;
return *this;
}
//习题14.21
class NoName
{
public:
NoName(Screen *p):ptr(new ScreenPtr(p)){}
ScreenPtr operator-> ()
{
return *ptr;
}
const ScreenPtr operator-> () const
{
return *ptr;
}
private:
ScreenPtr *ptr;
};
//习题14.22
/*
*==操作符需要在类中声明
* friend bool operator==(const ScreenPtr &lhs,
const ScreenPtr &rhs);
*/
inline
bool operator==(const ScreenPtr &lhs,const ScreenPtr &rhs)
{
return lhs.ptr == rhs.ptr;
}
inline
bool operator!=(const ScreenPtr &lhs,const ScreenPtr &rhs)
{
return !(lhs == rhs);
}