条款28 :避免返回handles指向对象内部成分
我们先看一个例子:
classPoint{
public:
Point(intx,inty);
voidsetX(intnewVal);
voidsetY(intnewVal);
};
structRectData{
Pointulhc;
Pointlrhc;
};
classRectangle{
public:
Point&upperLeft()const{returnpData->ulhc;}
Point&lowerRight()const{returnpData->lrhc;}
...
private:
std::tr1::shared_ptr<RectData>pData;
};
这个Rectangle类中的upperLeft(),lowerRight()这两个方法是返回左上点,和右下点。并不希望客户来修改,所以使用了const关键字来限定。
但是我们发现这两个函数返回的是pData的内部数据的引用。因此,并没有封装好。也就是说我们可以这样做:
Pointcoord1(1,1);
Pointcoord2(100,100);
constRectanglerec(coord1,coord2);
rec.upperLeft().setX(50);
我们还是可以修改的。虽然那个函数是一个const的。所以从这个例子中,我们可以得到以下的教训:
l 成员变量的封装性会被引用破坏。
l 如果const成员函数传出一个reference,后者所指的数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。
l 同样的,我们返回pointer,Iterator的效果和reference是一样的。
因此我们可以修改一下:
classRectangle{
public:
constPoint&upperLeft()const{returnpData->ulhc;}
constPoint&lowerRight()const{returnpData->lrhc;}
...
private:
std::tr1::shared_ptr<RectData>pData;
};
这样就解决了上面的那个问题:也就是说,这些函数让渡了读取权,但是涂写权仍然被禁止。
但是这样做也不是很好,因为upperLeft和lowerRight还是返回了“代表对象内部“的handles,有可能在其他场合带来问题。更明确的说会导致”dangling handles(空悬的号码牌):这种handles所指的对象不存在了“。考虑下面的例子:
classGUIObject{};
constRectangleboundingBox(constGUIObject&obj);
现在,客户可能这么使用。
GUIObject*pgo;
constPoint*pUpperLeft=&(boundingBox(*pgo).upperLeft());
你会发现(boundingBox(*pgo).upperLeft())这是一个point对象。但是当这一句执行完后,这个临时对象会被析构。这时,pUpperLeft会指向一个空的对象。也就出现了悬空现象。
因此,这就是为什么函数如果“返回一个handle代表对象内部成分“总是危险的原因。
但这并不意味着,绝对不可以让函数返回内部的handles。有时候你必须这样做。比如:
classFoo{
public:
int&operator[](constint);
constint&operator[](constint)const;
private:
vector<int>data;
}
int&Foo::operator[](constinti){
returndata[i];
}
constint&Foo::operator[](constinti)const{
returndata[i]
}
请记住:
- 避免返回handles指向对象的内部。遵守这个条款可增加封装性,帮助const成员函数更加像一个const,并将“虚号码牌“的可能性降低到最低。