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