Item 28:避免返回handles指向对象内部成分

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





 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值