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),那些数据会随着容器被销毁而销毁。尽管如此,这样的函数毕竟是例外,不是常态。





 

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...