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

来源:互联网 发布:查询重复数据的sql 编辑:程序博客网 时间:2024/05/16 15:35

条款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)的可能性降至最低.

0 0
原创粉丝点击