条款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,并将“虚号码牌“的可能性降低到最低。
原文地址:https://www.cnblogs.com/loveyakamoz/p/2772417.html