假设你正在操作一个Rectangle类。每个矩形可以通过左上角的点和右下角的点来表示。为了保证一个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

1. 由返回指向对象内部数据的引用所引发的两个问题

1.1 问题分析

因为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成员函数,因为它们只用来为客户提供获知矩阵包含哪些点的方法,并没有让客户修改矩阵(Item 3)。另一方面,两个函数都返回指向私有内部数据的引用——而此引用能够被调用者用来修改内部数据!举个例子:

 Point coord1(, );

 Point coord2(, );

 const Rectangle rec(coord1, coord2);

 // rec is a const rectangle from

 // (0, 0) to (100, 100)
rec.upperLeft().setX(); // now rec goes from
// (50, 0) to (100, 100)!

注意upperLeft的调用者是如何利用返回的指向rec内部的Point数据成员的引用来修改这个成员的。但是rec是const变量。

我们能从中学到两点。首先,一个数据成员的封装性同以这个数据成员的引用作为返回值的可访问级别最高(most accessible)的成员函数一致。在这种情况下,虽然ulhc和lrhc对于Rectangle来说是private的,它们实际上是public的,因为public函数upperLeft和lowerRight返回了指向它们的引用。第二,如果一个const成员函数返回指向数据的引用,而此数据被存储在当前对象之外,那么函数的调用者就能够修改这些数据。(这是bitwise constness局限性的附带结果Item 3)。

1.2 句柄(handles)不仅包含引用,也包含指针和迭代器

我们讨论的都是返回引用的成员函数,但是如果它们返回指针或者迭代器,同样原因导致的同样问题也将会存在。引用,指针和迭代器都是句柄(handles),返回一个指向对象内部数据的句柄常常有破坏封装型的风险。也会导致从const成员函数传递出去的对象的状态被修改掉。

1.3 内部数据(internals data)不仅包含数据成员,也包括成员函数

我们通常认为一个对象的“内部数据”只针对数据成员,但非public的成员函数也是对象内部数据的一部分。因此,禁止返回指向它们的句柄同样重要。这意味着绝不要从成员函数中返回一个指向更低访问级别的函数的指针。如果你这么做了,有效的访问级别就是访问级别更高的那个函数,因为客户可以获得访问级别更低的函数的指针,然后通过指针来调用此函数。

2. 解决上面两问题的方法,为引用添加const

返回指向成员函数指针的函数并不普通,让我们重新关注Rectangle类和它的upperLeft和lowerRight成员函数。我们发现的两个问题可以通过简单的为其返回值添加const来消除:

 class Rectangle {
public:
...
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
...
};

使用这个修改后的设计,客户可以读取定义一个矩形的点,但是不能修改它们。这意味着upperLeft和lowerRight的声明不再是一个谎言,因为它们不再允许调用者修改对象的状态。对于封装问题,我们的意图是让客户能够看到组成矩形的点,因此我们故意放松了封装型。更加重要的是,这是有局限性的“放松“:这些函数是只读的。

3. 返回const引用会引入新的问题

即便如此,upperLeft和lowerRight仍然返回了指向对象内部数据的句柄,这可能会在其它方面出现问题。特别是它能导致悬挂指针:指向部分对象的句柄不再存在。这种对象消失问题的最常见的根源在于按值返回的函数。举个例子,考虑一个为GUI对象返回边界框的函数,边界框用矩阵表示:

 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内部数据的引用,temp内部数据就是组成矩形的一个Point。pUpperLeft将会指向这个Ponit对象。到现在为止一切正常,但是还没完呢,因为在声明结束时,boundingBox的返回值——temp——将会被销毁,这将间接导致temp的Point对象被析构。这样使得pUpperLeft指向一个不再存在的对象;pUpperLeft在创建它的语句结束时变成了悬挂指针!

这也是为什么任何返回指向对象内部数据的句柄的函数都是危险的。不论这个句柄是一个指针,或者一个函数,或者一个迭代器。也不管这个函数有没有被声明为const。也不管成员函数返回的句柄是否为const。问题的关键在于函数有没有返回句柄,因为一旦返回了,就会有句柄比其指向的对象存在时间更长的危险。

4. 例外的情况

这并不意味着你永远不应该让一个成员函数返回一个句柄。有时候你必须这么做。举个例子,operator[]允许你从string或者vector中将单个元素摘出来,这些operator[]返回的就是指向容器内部数据的引用(Item 3)——容器被销毁的时候,它里面的数据也被销毁。但这样的函数只是一个例外。

5. 总结

避免返回指向对象内部数据的句柄(引用,指针或者迭代器)。不返回句柄可以增强封装性,帮助const成员函数的行为为真正的const,减少悬挂指针被创建的可能。

Effective c++ Item 28 不要返回对象内部数据(internals)的句柄(handles)的更多相关文章

  1. Effective C++ Item 28 避免返回对象内部数据的引用或指针

    本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie Item 31 经验:避免返回handles(包含 references.指针.迭代器)指向 ...

  2. 读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)

    假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...

  3. Effective JavaScript Item 55 接受配置对象作为函数參数

    接受配置对象作为函数參数 尽管保持函数接受的參数的顺序非常重要,可是当函数可以接受的參数达到一定数量时.也会让用户非常头疼: var alert = new Alert(100, 75, 300, 2 ...

  4. Effective C++:条款28:避免返回 handles 指向对象内部成员

    (一) 有时候为了让一个对象尽量小,能够把数据放在另外一个辅助的struct中,然后再让一个类去指向它.看以下的代码: class Point { public: Point(int x, int y ...

  5. [Effective C++ --028]避免返回handles指向对象内部成分

    假设程序涉及矩形.每个矩形由其左上角和右下角表示.为了让Rectangle对象尽可能小,可能把定义矩形的点放在一个辅助的struct内再让Rectangle去指它: class Point { // ...

  6. Effective C++ Item 46 当需要投你非成员函数定义模板

    本文senlie原版的,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:当我们编写一个 class template, 而它所提供之"与此 temp ...

  7. 读书笔记 effective c++ Item 3 在任何可能的时候使用 const

    Const可以修饰什么?   Const 关键字是万能的,在类外部,你可以用它修饰全局的或者命名空间范围内的常量,也可以用它来修饰文件,函数和块作用域的静态常量.在类内部,你可以使用它来声明静态或者非 ...

  8. c++逆向分析----返回对象

    对象不使用默认析构函数 class Test { public: char cNum1; int iNum2; int* pInt; }; Test _ReturnObject() { Test st ...

  9. Effective C++ -----条款28:避免返回handles指向对象内部成分

    避免返回handles(包括reference.指针.迭代器)指向对象内部.遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handle ...

随机推荐

  1. 【转】java调用存储过程和函数

    一.概述 如果想要执行存储过程,我们应该使用 CallableStatement 接口. CallableStatement 接口继承自PreparedStatement 接口.所以CallableS ...

  2. 按ctrl + c 播放下一曲音乐

    ./a.out . #include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<dirent. ...

  3. IE6下完美兼容css3圆角和阴影属性的htc插件PIE.htc

    1.(推荐:)css插件PIE.htc,这个才是真正完美兼容css3的圆角和阴影属性在IE6环境下使用的效果,但要注意的是:下面的代码必须写在html文件的head标签内,否则无效(不能从外部引用下面 ...

  4. 仿网易邮箱5.0(四):信息提示插件(tips.js)

    信息提示插件,在平常的开发中也是经常乃至的一个插件,像是一些辅助信息的提示,如:加载成功.提交信息成功或失败等等.这个插件在163邮箱中用在切换标签时提示加载状态. 下面我们先来分析一下这个小插件需要 ...

  5. #图# #SPFA# ----- codevs1021 玛丽卡

    codevs1021 玛丽卡 题目描述 Description麦克找了个新女朋友,玛丽卡对他非常恼火并伺机报复.因为她和他们不住在同一个城市,因此她开始准备她的长途旅行.在这个国家中每两个城市之间最多 ...

  6. eclipse安装git插件

    用Eclipse开发,有时需要团队协作,git就是个比较好的选择.下面简单介绍一下git插件的安装方法:   1.Help -- install new software 打开插件安装界面 2.点ad ...

  7. requests从api中获取数据并存放到mysql中

    python的requests库是一个非常强大的库,requests的安装方法十分简单,用: pip install requests 即可安装requests,安装成功后: import reque ...

  8. Mac系统安装Aircrack-ng破解wifi密码(2)

    我们上一篇文章说过如何通过Aircrack-ng, 破解使用WPA/PSK加密方式的路由密码, 这一篇介绍的是使用Aircrack-ng破解使用WEP加密方式的路由密码: WEP和WPA/PSK的区别 ...

  9. Form开发:字段关系-消息-快速编码-参数和系统变量

     1.字段关系  清除依赖字段:在挂LOV的名称字段的WHEN-VALIDATE-ITEM调用:app_field.clear_dependent_fields    设置字段依赖:在主字段的WHEN ...

  10. LCD驱动移植在在mini2440(linux2.6.29)和FS4412(linux3.14.78)上实现对比(deep dive)

    1.Linux帧缓冲子系统 帧缓冲(FrameBuffer)是Linux为显示设备提供的一个接口,用户可以将帧缓冲看成是显示内存的一种映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作 ...