避免返回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; // ulhc = 左上角Point lrhc; // lrhc = 右下角
};
class Rectangle { // 矩形类...private:std::tr1::shared_ptr 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; }...
};
这段代码是自我矛盾的:
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)!
得到的教训:
补充:
解决方案:对返回类型加上const
class Rectangle {public:...const Point& upperLeft() const { return pData->ulhc; }const Point& lowerRight() const { return pData->lrhc; }...
};
这样之后,就提供了一个有限度的放松封装:这些函数只让渡读取权。涂写权仍然是被禁止的。
也就是说这种 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) !
这并不意味绝对不可以让成员函数返回handle。有时候必须那么做。例如operator[]就允许“摘采”strings和vectors的个别元素,而这些operator[]s就是返回references指向“容器内的数据”(见条款3),那些数据会随着容器被销毁而销毁。尽管如此,这样的函数毕竟是例外,不是常态。