条款28:避免返回handles指向对象内部成分

来源:互联网 发布:江西网络问政 编辑:程序博客网 时间:2024/05/01 21:16
假如定义一个矩形。为了让Rectangle对象尽可能小,我们不会把定义的这些点(左上角和右下角)置于对象内,而是用一个辅助struct再去指向它。如下代码:
class point      //点类{  public:  point(int x,int y);  ...  void setX(int val);  void setY(int val);  ...}; struct RectData   //点数据表示一个矩形{  point  ul;  //upper left  point  lr;  //lower right}; class Rectangle{  ...  private:   std::tr1::shared_ptr<RectData> pData;  ...};

再如我们要知道矩形的范围,于是提供两成员函数,(条款20的忠告by reference传递用户自定义类型通常更高效),于是返回引用代表底层point对象。如下代码:
//可通过编译,但是是错误的
class Rectangle{  public:         ...  point&  uLeft() const{return pData->ul;}  //upper left  point&  lRight() const{return  pData->lr;} //low right  ...};

一方面uLeft()和lRight()被声明为const函数,只是为提供一个获得坐标的方法(而不是去改变它),另一方面返回的却是对象内部private 数据的引用,调用者可能通过这些引用改变数据。如下代码:
point p1(0,0);point p2(100,100);const Rectangle rec(p1,p2); //const矩形,从(0,0)到(100,100)rec.uLeft.setX(50);     //被改变为(50,0)(100,100)


上面情况给了两个教训:
1、成员变量的封装性最多只等于返回其引用的成员函数的访问级别。ul和lr虽然是private的,但public函数uLfet和lRight传出了他们的引用。
2、 const成员函数传出一个引用,而传出的该引用又与该对象自身有关联、且存储于对象之外,则可以通过该引用改变数据。(bitwise constness附带结果,条款3)
 
reference、指针、迭代器统统都是所谓的handles(用来取得某个对象),但返回一个对象内部的handles,会带来“降低对象封装性”的风险。所以绝不能让成员函数返回一个指针指向“访问级别较低“的成员函数,如果这样会造成访问级别较低的成员函数的访问级别提高至访问级别较高成员函数的水平。
 
解决方法,但不是很完善,如下代码:
class Rectangle{         public:                   ...         const point&upperLeft() const{return pData->ul;}         const point&lowerRight() const{return pData->lr;}         ...};

这样客户就可以读取矩形矩形,但是不能修改。
尽管如此,但还是返回了对象内的handle,很可能导致空handle的问题,如下代码:
class GDIObject{...}; //GDIObject对象const Rectangle Box(const GDIObject& obj);  //by value方式返回GDI对象的外框矩形 //可能有的客户这么使用函数GDIObject* pgo; //让pgo指向一个GDIObject对象...const point* puLeft=&(Box(*pgo).upperLeft());  //返回外框矩形左上角

对Box的调用返回的是一个临时的Rectangle对象,于是puLeft指向临时对象的内部成分。即临时对象中的point,当语句结束,Box()的返回值“临时对象析构”,则puLeft指向一个空的对象,变成空悬。
这也说明了为什么函数 “返回一个handle代表对象内部成分”总是危险的原因,无论这handle是指针,迭代器,引用,或者为const,也不论那个返回handle的成员函数是否为const,一旦有handle对象传出去,就可能出现“handle比其所指对象更长寿”的风险。
但也并不意味着你绝不能这么做,如string,vector 的个别元素,operator[]s就是返回引用指向“容器内数据”,但毕竟是例外而非常态。
需要记住的:1、避免返回handle(reference 指针 迭代器)指向对象内部。遵守这个意见可增加封装性,帮助const成员函数像真正意义上的const。并降低悬空handle(dangling handles)的可能性。
0 0
原创粉丝点击