您的位置:首页 > 编程语言 > C语言/C++

Effective C++——条款28(第5章)

2015-09-07 10:09 369 查看

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

Avoid returning "handles" to object internals

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

class Point {
public:
    Point(int x, int y);
    ...
    void setX(int newVal);
    void setY(int newVal);
    ...
};
struct RectData {
    Point ulhc;            // upper left-hand comer
    Point lrhc;            // lower right-hand comer
};
class Rectangle {
    ...
private:
    std::tr1::shared_ptr<RectData> pData;
};
这个 class 还提供了upperLeft函数和lowerRight函数,Point是个用户自定义类型,所以根据条款20的忠告(以by reference方式传递用户自定义类型往往比by value方式传递更高效),这些函数于是范虎reference,代表底层的Point对象:

class Rectangle {
public:
    ...
    Point& upperLeft() const { return pData->ulhc; }
    Point& lowerRight() const { return pData->lrhc; }
    ...
};
这样的设计可通过编译,但确实错误的.实际上它是自我矛盾的.一方面upperLeft和lowerRight被声明为 const 成员函数,因为它们的目的只是为了提供客户一个得知Rectangle相关坐标点的方法,而不是让客户修改Rectangle(详见条款3).另一方面这两个函数却返回reference指向
private 内部数据
,调用者于是可通过这些reference更改内部数据!

这立刻给出两个教训:第一,成员变量的封装性最多只等于"返回其reference"的函数的访问级别.本例中虽然ulhc和lrhc都被声明为 private,但它们实际上却是 public,因为 public 函数upperLeft和lowerRight传出了它们的reference.第二,如果 const 成员函数传出一个reference,后者所指的数据与对象自身有关联,而它又被存储在对象外,那么这个函数的调用者可以修改该那笔数据.这正是bitwise
constness的一个附带结果,详见条款3.

上述所说的每件事都是由于"成员函数返回reference".如果它们返回的是指针或迭代器,相同的情况还是发生,原因也相同.Reference,指针和迭代器统统都是所谓的handles(号码牌,用来取得某个对象),而返回一个"代表对象内部数据"的handle,随之而来的便是"降低对象封装性"的风险.

在前两个函数身上的问题可以轻松去除,只要对它们的返回类型加上 const
即可
:

class Rectangle {
public:
    ...
    const Point& upperLeft() const { return pData->ulhc; }
    const Point& lowerRigth() const { return pData->lrhc; }
    ...
};
有了这样的改变,客户可以读取矩形的Point,但不能修改它们.

即使如此,函数如果"返回一个handle代表对象内部成分"还是危险的,可能变为悬挂的(dangling).原因是,有个handle被传出去,一旦如此就有"handle比其所指对象更长寿"的风险(可能对象被析构了,但handle还存在(它指向对象内部)).

注意:

避免返回handle(包括reference,指针,迭代器)指向对象内部.遵守这个条款可增加封装性,帮助 const 成员函数的行为像个 const,并将发生"悬挂号码牌"(dangling handle)的可能性降至最低.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: