Item 28:避免返回handles指向对象内部成分
创始人
2024-05-31 20:07:14
0

0.概述

避免返回handles(包括references、指针、迭代器)指向对象内部。这样可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。

1. 举例:封装性

假设你的程序涉及矩形。每个矩形由其左上角和右下角表示。为了让一个Rectangle对象尽可能小,不把定义矩形的这些点存放在Rectangle对象内,而是放在一个辅助的struct内再让 Rectangle去指它:

class Point { // “点”类public:Point(int x, int y);...void setX(int newVal);void setY(int newVal);...
};
struct RectData { // 表示矩形的类Point ulhc; // ulhc = 左上角Point lrhc; // lrhc = 右下角
};
class Rectangle { // 矩形类...private:std::tr1::shared_ptr pData;
};

Rectangle的使用者必须能够计算 Rectangle 的范围,所以这个class提供upperLeft函数和 lowerRight函数。Point是个用户自定义类型,根据Item20:以by reference方式传递用户自定义类型往往比以by value方式传递更高效,这些函数于是返回references,代表底层的 Point对象:

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

这段代码是自我矛盾的:

  • 一方面upperLeft和lowerRight被声明为const成员函数,因为它们的目的只是为了提供客户一个得知Rectangle相关坐标点的方法,而不是让客户修改Rectangle(见条款3)。
  • 另一方面两个函数却都返回references 指向private内部数据,调用者于是可通过这些references更改内部数据!例如:
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2); //从(0,0)到(100,100)的矩形
rec.upperLeft().setX(50); // 现在从(50,0)到(100,100)!

得到的教训:

  • 成员变量的封装性最多只等于“返回其reference”的函数的访问级别。本例之中虽然ulhc和lrhc都被声明为private,它们实际上却是public,因为public函数upperLeft和lowerRight传出了它们的引用
  • 如果const成员函数传出一个reference,所指数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据(bitwise constness 的附带结果)。

补充:

  • 上面的例子是返回引用的情况,同样也适用返回指针和迭代器
  • 所谓的对象内部不仅是成员变量,也包括私有或保护的成员函数,这意味着绝不该让成员函数返回一个指针指向访问级别较低的成员函数。

解决方案:对返回类型加上const

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

这样之后,就提供了一个有限度的放松封装:这些函数只让渡读取权。涂写权仍然是被禁止的。


2.举例:空悬的handles

也就是说这种 handles 所指东西(的所属对象)不复存在。这种“不复存在的对象”最常见的来源就是函数返回值。例如某个函数返回GUI对象的外框(bounding box),这个外框采用矩形形式:

class GUIObject { ... };
const Rectangle // 以by value的方式返回一个矩形
boundingBox(const GUIObject& obj);

客户可能会这样使用这个函数:

GUIObject *pgo; // make pgo point to some GUIObject
...
const Point *pUpperLeft = //取得一个指针指向左上角点
&(boundingBox(*pgo).upperLeft()); 

对boundingBox的调用获得一个新的、暂时的Rectangle对象。这个对象没有名称,所以我们权且称它为temp。

随后upperLeft作用于temp 身上,返回一个reference指向temp 的一个内部成分,更具体地说是指向一个用以标示temp的Points。于是pUpperLeft 指向那个Point对象。

但在那个语句结束之后,boundingBox的返回值,也temp将被销毁,间接导致temp内的Points 析构。最终导致 pUpperLeft指向一个不再存在的对象;也就是说一旦产出pupperLeft 的那个语句结束,pupperLeft也就变成空悬、虚吊(dangling) !

3.特殊情况

这并不意味绝对不可以让成员函数返回handle。有时候必须那么做。例如operator[]就允许“摘采”strings和vectors的个别元素,而这些operator[]s就是返回references指向“容器内的数据”(见条款3),那些数据会随着容器被销毁而销毁。尽管如此,这样的函数毕竟是例外,不是常态。





 

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...