侧边栏壁纸
博主头像
如此肤浅

但行好事,莫问前程!

  • 累计撰写 52 篇文章
  • 累计创建 6 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

条款28 避免返回 handles 指向对象内部成分

如此肤浅
2022-06-05 / 0 评论 / 0 点赞 / 23 阅读 / 2,070 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-05,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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

问题描述

假设有个类处理矩形,包含矩形左上角和右下角相关信息。为了使一个 Rectangle 对象尽量小,于是不把定义矩形的这些点存放在 Rectangle 对象内,而放在一个辅助的 struct 内再让 Rectangle 去指向它:

class Point { // 这个类用来表述“点”
public:
	Point(int x, int y);
    ...
    void setX(int newVal);
    void setY(int newVal);
    ...
};
struct RectData { // “点”数据,用来表示一个矩形
	Point ulhc; // 左上角
    Point lrhc; // 右上角
};

class Rectangle {
public:
	...
    // 为了防止修改矩形,声明为const成员函数;
    // 为了提高速度,返回引用,但会出错。
    Point& upperLeft() const { return pData->ulhc; }
    Point& lowerRight() const { return pData->lrhc; }
private:
	std::tr1::shared_ptr<RectData> pData;
};

上述代码中的两个函数都返回 references 指向 private 内部的数据,调用者可通过这些 references 更改内部数据!如下:

Point coord1(0, 0);
Point coord2(100, 100);
// rec是一个const矩形,其位置由两个点coord1和coord2决定
const Rectangle rec(coord1, coord2); 
rec.upperLeft().setX(50); // 修改了其中的值

这里请注意,upperLeft 的调用者能够使用被返回的 reference 来更改成员。但 rec 其实应该是不可变的(const)!

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

上面情况的产生原因都是由于“成员函数返回 references”,如果它们返回的是指针或迭代器,会发生相同的情况,原因也相同。References、指针和迭代器都是所谓的handles

解决方法

上述问题的解决方式是:对函数的返回类型上加 const 即可:

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

加了 const 后,客户可以读取矩形的 Points,但不能修改它们。

但即便如此,upperLeft 和 lowerRight 还是返回了“代表对象内部”的 handles,可能在某些场合带来 dangling handles(空悬的号码牌)(即,这种 handles 所指东西(的所属对象)不复存在)问题。如下:

class GUIObject { ... };
const Retangle boundingBox(const GUIObject& obj);
GUIObject* pgo;  // 让pgo指向某个GUIObject
...
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());

上述代码中,对 boundingBox 的调用获得一个新的、临时的 Rectangle 对象。随后 upperLeft 作用于这个临时对象身上,返回一个 reference 指向临时对象的一个内部成分。于是 pUpperLeft 指向那个 Point 对象。但在语句结束之后,boundingBox 的返回值,也就是临时对象将被销毁,而那间接导致临时对象内的 Points 被析构。最终导致 pUpperLeft 指向一个不再存在的对象;也就是说一旦产出 pUpperLeft 的那个语句结束,pUpperLeft 也就变成空悬、虚吊(dangling)!

这就是为什么函数如果“返回一个 handle 代表对象内部成分”总是危险的原因。不论这所谓的 handle 是个指针或迭代器或 reference,也不论这个 handle 是否为 const,也不论那个返回 handle 的成员函数是否为 const。

但这不意味着绝不可以让成员函数返回 handle,因为这样的函数毕竟是例外,不是常态。

0

评论区