条款28:避免返回handles指向对象内部成分
(Avoid returning "handles" to object internals.)
内容:
这里所谓的handles一般包括内部对象的references、pointer、iterator,这条款是如何产生的呢?理由是什么
呢?我先不必急着回答你这些问题,我们先来看一个例子,假设我们的程序中要用到矩形,于是我们写了Rectangle类去
描述它,该类的内部对象为矩形左上角(upper left-hand corner)、右下角(Lower right-hand corner)两个Point
对象,为了得到这两个内部对象数据我们提供了upperLeft与lowerRight函数接口.由于这里Point类是自定义类型,故
我们先开始定义该类:
class Point{
public:
Point(int x,int y);
...
void setX(int value);
void setY(int value);
...
};
接下来我们开始写Rectangle类,考虑到让该类尽可能的小,我决定不把Point点定义在类的内部,而是通过在其内部
放一个辅助类对象指针,该辅助类中存放两个Point对象.这种做法很常见,下面我们来实现:
struct RectData{
Point upperLeftPoint,lowerRightPoint;
};
class Rectangle{
public:
...
Point& upperLeft()const{ //返回内部对象数据handle
return pData->upperLeftPoint;
}
Point& lowerRight()const{ //返回内部对象数据handle
return pData->lowerRightPoint;
}
private:
std::tr1::shared_ptr<RectData> pData;
};
一看代码我们就会很轻易的发现这里存在一个安全隐患:该类提供的两个接口返回的私有区域内部对象,那么客户
使用该类的时候,有可能修改了该内部对象的数据,而实际上我们是不想让客户拥有这个权限的.例如:
Point coord1(0,0);
Point coord2(100,100);
const Rectangle rec(coord1,coord2);
rec.upperLeft().setX(50);//wow, it changes the internal data,that's amazing!
从这个例子我们至少可以了解以下两点:(1)返回内部对象相当于让内部对象变为对用户可见即相当于让其处于
public区段中;(2)客户可以通过返回内部对象handle接口函数修改private对象属性,这显然不是我们想要的结果
.怎么解决?其中的一个解决方案就是在返回类型上加上const即可:
class Rectangle{
public:
...
const Point& upperLeft()const{return pData->upperLeftPoint;}
const Point& lowerRight()const{return pData->lowerRightPoint;}
...
};
问题似乎得到了解决,我们愿意让客户看到Rectangle的外围Points,所以这里是蓄意放松封装.更重要的是这是
一个有限度的放松:这些函数只让出读取权,而修改权限仍然是被禁止的.在你说这个解决方案完全perfect之前,这时
客户默默的写下了下面这段"恐怖"的代码:
class GUIObject{...};//GUI base class
const Rectangle boundingBox(const GUIObject& obj);//该函数返回GUI对象的外框.
现在客户有可能这么用这个函数:
GUIObject* pObject = NULL;
... //create GUIObject object
const Point* pUpperLeft = &( boudingBox(*pObject).upperLeft());
喔欧,看出问题了没有?boudingBox返回了一个临时对象对象(姑且称之为tempt),upperLeft返回的是tempt
对象的内部数据的引用,那么现在pUpperLeft就指向该tempt对象的内部对象的地址.这里你别忘记啰:该语句调用
结束以后tempt对象自动销毁,此时的pUpperLeft指向的就是一个不存在的对象.pUpperLeft也就是变成空悬、
悬挂了(dangling)!呵呵.
今天我们就讨论到这里,如有什么问题的话,请留言.
请记住:
■ 避免返回handles(包括reference,pointer,iterator)指向内部对象.遵守这个条款可增加封装性,帮助const
成员函数的行为像个const,并将发生"虚吊号码牌"(dangling handles)的可能性降至最低.