读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)
假设你正在操作一个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)的更多相关文章
- Effective c++ Item 28 不要返回对象内部数据(internals)的句柄(handles)
假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...
- 读书笔记 effective c++ Item 3 在任何可能的时候使用 const
Const可以修饰什么? Const 关键字是万能的,在类外部,你可以用它修饰全局的或者命名空间范围内的常量,也可以用它来修饰文件,函数和块作用域的静态常量.在类内部,你可以使用它来声明静态或者非 ...
- Effective C++ Item 28 避免返回对象内部数据的引用或指针
本文为senlie原创.转载请保留此地址:http://blog.csdn.net/zhengsenlie Item 31 经验:避免返回handles(包含 references.指针.迭代器)指向 ...
- 读书笔记 effective c++ Item 21 当你必须返回一个对象的时候,不要尝试返回引用
1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销.对纯净的按引用 ...
- 读书笔记 effective c++ Item 10 让赋值运算符返回指向*this的引用
一个关于赋值的有趣的事情是你可以将它们链在一起: int x, y, z; x = y = z = ; // chain of assignments 同样有趣的是赋值采用右结合律,所以上面的赋值链被 ...
- 读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数
1. 问题的引入——将operator*模板化 Item 24中解释了为什么对于所有参数的隐式类型转换,只有非成员函数是合格的,并且使用了一个为Rational 类创建的operator*函数作为实例 ...
- 读书笔记 effective c++ Item 54 让你自己熟悉包括TR1在内的标准库
1. C++0x的历史渊源 C++标准——也就是定义语言的文档和程序库——在1998被批准.在2003年,一个小的“修复bug”版本被发布.然而标准委员会仍然在继续他们的工作,一个“2.0版本”的C+ ...
- 读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...
- 读书笔记 effective c++ Item 15 在资源管理类中提供对原生(raw)资源的访问
1.为什么需要访问资源管理类中的原生资源 资源管理类是很奇妙的.它们是防止资源泄漏的堡垒,没有资源泄漏发生是设计良好的系统的一个基本特征.在一个完美的世界中,你需要依赖这样的类来同资源进行交互,绝不 ...
随机推荐
- 【转】iBatis.Net的SqlMap.config文件
转自:http://www.xuebuyuan.com/579671.html iBatis.Net基本的运行环境配置主要由两个文件组成,分别是SqlMap.config和Provider.con ...
- Mininet 系列实验(三)
实验内容 基础 Mininet 可视化界面进行自定义拓扑及拓扑设备自定义设置,实现自定义脚本应用. 参考 Mininet可视化应用 实验环境 虚拟机: Oracle VM VirtualBox Ubu ...
- 命令行Scp的使用----远程拷贝文件
1.用CRT分别连上两台需要传输文件的linux系统服务器,并检查防火墙是否关闭. 查看防火墙状态: /etc/init.d/iptables status 若防火墙启用,暂时关闭防火墙: /etc/ ...
- android与H5互相调用
市面上很多android软件都有内嵌H5的,主要是为了节约成本,提高开发效率,其实现原理主要是通过Java代码和JavaScript代码的互相调用来实现. Java调用Js 1,webview初始化: ...
- Android 65535 问题与 MultiDex分包
Android Multidex 遇到的问题 http://blog.csdn.net/wangbaochu/article/details/51178881 Android 使用android-su ...
- bzoj 4358: permu 莫队
第一步先莫队分块. 对于每一块l~r,初始右端点设为r+1,然后每个询问先将右端点往右移,然后处理询问在l~r之间的部分,最后用一个栈再把l~r的复原. 具体来说是维护两个数组now1和now2,一个 ...
- Luogu 1020 导弹拦截(动态规划,最长不下降子序列,二分,STL运用,贪心,单调队列)
Luogu 1020 导弹拦截(动态规划,最长不下降子序列,二分,STL运用,贪心,单调队列) Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺 ...
- ASP.NET MVC开发,编辑页面和添加页面基本相同,我们控制器 Add Edit是共用同一个View吗?
http://q.cnblogs.com/q/51693/ 这种的话,一般公用就好了.,如下的写法: [HttpGet] public ActionResult UserManage(int user ...
- UnityShader 序列帧动画效果
实现原理:主要思想是设置显示uv纹理的大小,并逐帧修改图片的uv坐标. 实现步骤 1.我们首先用_Time.y和速度属性_Speed相乘得到模拟的时间. 2.然后我们用time除以_Horizonta ...
- increment/decrement/dereference
#include <vector> #include <deque> #include <algorithm> #include <iostream> ...