首页 > 代码库 > Effective C++:条款28:避免返回 handles 指向对象内部成员

Effective C++:条款28:避免返回 handles 指向对象内部成员

(一)

有时候为了让一个对象尽量小,可以把数据放在另外一个辅助的struct中,然后再让一个类去指向它。看下面的代码:

class Point {
public:
	Point(int x, int y);
	void setX(int newVal);
	void setY(int newVal);
};

struct RectData {
	Point ulhc;
	Point lrhc;
};

class Rectangle {
public:
	Point& upperLeft() const { return pData->ulhc; }
	Point& lowerRight() const { return pData->lrhc; }
private:
	std::tr1::shared_ptr<RcetData> pData;
};
这样的设计看上去很beautiful。但是却是错误的,实际上它是自相矛盾的,看下面的代码:

Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);
rec.upperleft().setX(50);
错误的理由:(1)upperLeft()跟lowerRight()这两个函数都是const的,所以客户不能修改Rectangle;

(2)两个函数都返回reference指向private内部数据,调用者于是可通过这些reference更改内部数据。

upperLeft的调用者能够使用被返回的引用来更改成员。但rec其实应该是不可变的(const)!

所以上面那种类的设计是错误的!!!

所以从这个例子中,我们可以得到以下的教训:

(1)成员变量的封装性会被引用破坏。

(2)如果const成员函数传出一个reference,后者所指的数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。

同样的道理,返回对象的引用、指针、迭代器都会造成这种局面,它们都是“句柄”。返回一个代表对象内部数据的句柄,会降低对象的封装。


(二)解决办法:

只要对这两个函数的返回类型加上const即可:

class Rectangle {
public:
	const Point& upperLeft() const { return pData->ulhc; }
	const Point& lowerRight() const { return pData->lrhc; }
private:
	std::tr1::shared_ptr<RcetData> pData;
};
有了这样的改变,客户就只能读取矩形的Points,但是不能涂写它们。

(三)

上面那种解决方法虽然确保了内部对象不会被修改。但是却可能导致dangling handles(空悬的号码牌):这种handles所指东西(的所属对象)不复存在。

这种“不复存在的对象”最常见的来源就是函数返回值。

class GUIObject {...};
const Rectangle boundingBox(const GUIObject& obj);

GUIObject* pgo;
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
你会发现(boundingBox(*pgo).upperLeft())这是一个point对象。但是当这一句执行完后,这个临时对象temp会被析构。这时,pUpperLeft会指向一个空的对象。也就出现了悬空现象。

因此,这就是为什么函数如果“返回一个handle代表对象内部成分“总是危险的原因。


请记住:

(1)避免返回handles指向对象的内部。遵守这个条款可增加封装性,帮助const成员函数更加像一个const,并将“虚号码牌“的可能性降低到最低。