分享
 
 
 

翻译:Effective C++, 3rd Edition, Item 28: 避免返回对象内部构件的“句柄”

王朝c/c++·作者佚名  2006-01-31
窄屏简体版  字體: |||超大  

Item 28: 避免返回对象内部构件的“句柄”

假设你正在一个包含矩形的应用程序上工作。每一个矩形都可以用它的左上角和右下角表示出来。为了将一个 Rectangle 对象保持在较小状态,你可能决定那些点的定义的域不应该包含在 Rectangle 本身之中,更合适的做法是放在一个由 Rectangle 指向的辅助的结构体中:

class Point { // class for representing points

public:

Point(int x, int y);

...

void setX(int newVal);

void setY(int newVal);

...

};

struct RectData { // Point data for a Rectangle

Point ulhc; // ulhc = " upper left-hand corner"

Point lrhc; // lrhc = " lower right-hand corner"

};

class Rectangle {

...

private:

std::tr1::shared_ptr<RectData> pData; // see Item 13 for info on

}; // tr1::shared_ptr

由于 Rectangle 的客户需要有能力操控 Rectangle 的区域,因此类提供了 upperLeft 和 lowerRight 函数。可是,Point 是一个用户定义类型,所以,留心 Item 20 关于在典型情况下,以传引用的方式传递用户定义类型比传值的方式更加高效的观点,这些函数返回引向底层 Point 对象的引用:

class Rectangle {

public:

...

Point& upperLeft() const { return pData->ulhc; }

Point& lowerRight() const { return pData->lrhc; }

...

};

这个设计可以编译,但它是错误的。实际上,它是自相矛盾的。一方面,upperLeft 和 lowerRight 是被声明为 const 的成员函数,因为它们被设计成仅仅给客户提供一个获得 Rectangle 的点的方法,而不允许客户改变这个 Rectangle(参见 Item 3)。另一方面,两个函数都返回引向私有的内部数据的引用——调用者可以利用这些引用修改内部数据!例如:

Point coord1(0, 0);

Point coord2(100, 100);

const Rectangle rec(coord1, coord2); // rec is a const rectangle from

// (0, 0) to (100, 100)

rec.upperLeft().setX(50); // now rec goes from

// (50, 0) to (100, 100)!

请注意这里,upperLeft 的调用者是怎样利用返回的 rec 的内部 Point 数据成员的引用来改变这个成员的。但是 rec 却被期望为 const!

这直接引出两条经验。第一,一个数据成员被封装,但是具有最高可访问级别的函数还是能够返回引向它的引用。在当前情况下,虽然 ulhc 和 lrhc 被声明为 private,它们还是被有效地公开了,因为 public 函数 upperLeft 和 lowerRight 返回了引向它们的引用。第二,如果一个 const 成员函数返回一个引用,引向一个与某个对象有关并存储在这个对象本身之外的数据,这个函数的调用者就可以改变那个数据(这正是二进制位常量性的局限性(参见 Item 3)的一个副作用)。

我们前面做的每件事都涉及到成员函数返回的引用,但是,如果它们返回指针或者迭代器,因为同样的原因也会存在同样的问题。引用,指针,和迭代器都是句柄(handle)(持有其它对象的方法),而返回一个对象内部构件的句柄总是面临危及对象封装安全的风险。就像我们看到的,它同时还能导致 const 成员函数改变了一个对象的状态。

我们通常认为一个对象的“内部构件”就是它的数据成员,但是不能被常规地公开访问的成员函数(也就是说,它是 protected 或 private 的)也是对象内部构件的一部分。同样地,不要返回它们的句柄也很重要。这就意味着你绝不应该有一个成员函数返回一个指向拥有较小的可访问级别的成员函数的指针。如果你这样做了,它的可访问级别就会与那个拥有较大的可访问级别的函数相同,因为客户能够得到指向这个拥有较小的可访问级别的函数的指针,然后就可以通过这个指针调用这个函数。

无论如何,返回指向成员函数的指针的函数是难得一见的,所以让我们把注意力返回到 Rectangle 类和它的 upperLeft 和 lowerRight 成员函数。我们在这些函数中挑出来的问题都只需简单地将 const 用于它们的返回类型就可以排除:

class Rectangle {

public:

...

const Point& upperLeft() const { return pData->ulhc; }

const Point& lowerRight() const { return pData->lrhc; }

...

};

通过这个修改的设计,客户可以读取定义一个矩形的 Points,但他们不能写它们。这就意味着将 upperLeft 和 upperRight 声明为 const 不再是一句空话,因为他们不再允许调用者改变对象的状态。至于封装的问题,我们总是故意让客户看到做成一个 Rectangle 的 Points,所以这是封装的一个故意的放松之处。更重要的,它是一个有限的放松:只有读访问是被这些函数允许的,写访问依然被禁止。

虽然如此,upperLeft 和 lowerRight 仍然返回一个对象内部构件的句柄,而这有可能造成其它方面的问题。特别是,这会导致空悬句柄:引用了不再存在的对象的构件的句柄。这种消失的对象的最普通的来源就是函数返回值。例如,考虑一个函数,返回在一个矩形窗体中的 GUI 对象的 bounding box:

class GUIObject { ... };

const Rectangle // returns a rectangle by

boundingBox(const GUIObject& obj); // value; see Item 3 for why

// return type is const

现在,考虑客户可能会这样使用这个函数:

GUIObject *pgo; // make pgo point to

... // some GUIObject

const Point *pUpperLeft = // get a ptr to the upper

&(boundingBox(*pgo).upperLeft()); // left point of its

// bounding box

对 boundingBox 的调用会返回一个新建的临时的 Rectangle 对象。这个对象没有名字,所以我们就称它为 temp。于是 upperLeft 就在 temp 上被调用,这个调用返回一个引向 temp 的一个内部构件的引用,特别是,它是由 Points 构成的。随后 pUpperLeft 指向这个 Point 对象。到此为止,一切正常,但是我们无法继续了,因为在这个语句的末尾,boundingBox 的返回值—— temp ——被销毁了,这将间接导致 temp 的 Points 的析构。接下来,剩下 pUpperLeft 指向一个已经不再存在的对象;pUpperLeft 空悬在创建它的语句的末尾!

这就是为什么任何返回一个对象的内部构件的句柄的函数都是危险的。它与那个句柄是指针,引用,还是迭代器没什么关系。它与是否受到 cosnt 的限制没什么关系。它与那个成员函数返回的句柄本身是否是 const 没什么关系。全部的问题在于一个句柄被返回了,因为一旦这样做了,你就面临着这个句柄比它引用的对象更长寿的风险。

这并不意味着你永远不应该让一个成员函数返回一个句柄。有时你必须如此。例如,operator[] 允许你从 string 和 vector 中取出单独的元素,而这些 operator[]s 就是通过返回引向容器中的数据的引用来工作的(参见 Itme 3)——当容器本身被销毁,数据也将销毁。尽管如此,这样的函数属于特例,而不是惯例。

Things to Remember

避免返回对象内部构件的句柄(引用,指针,或迭代器)。这样会提高封装性,帮助 const 成员函数产生 cosnt 效果,并将空悬句柄产生的可能性降到最低。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有