1. 矩形案例引出的问题
在矩形应用程序中,Rectangle
类为保持较小状态,将表示矩形两角点的Point
数据放在辅助结构体RectData
中,通过智能指针pData
指向。为方便客户操作矩形区域,提供upperLeft
和lowerRight
函数返回内部Point
对象的引用:
class Point {
public:
Point(int x, int y);
void setX(int newVal);
void setY(int newVal);
};
struct RectData {
Point ulhc;
Point lrhc;
};
class Rectangle {
private:
std::tr1::shared_ptr<RectData> pData;
public:
Point& upperLeft() const { return pData->ulhc; }
Point& lowerRight() const { return pData->lrhc; }
};
此设计虽能编译,但存在矛盾。upperLeft
和lowerRight
声明为const
成员函数,本意是不允许客户改变Rectangle
对象,然而它们返回的内部数据引用却使客户能修改内部数据,破坏了对象的const
特性。例如:
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);
rec.upperLeft().setX(50);
这里,rec
本应是const
矩形,却因返回的引用被修改。同时,这也揭示了数据成员虽被封装,但通过公共函数返回内部引用,实际上将内部数据公开了。
2. 句柄的风险及涵盖范围
引用、指针和迭代器都属于 “句柄”。返回对象内部构件的句柄不仅会危及对象封装安全,还可能导致const
成员函数改变对象状态。不仅数据成员,protected
或private
的成员函数也属于对象内部构件,同样不应返回指向它们的句柄,否则会提升其访问级别。例如,若有成员函数返回指向private
成员函数的指针,客户就能通过该指针调用此private
函数。
3. 改进设计及遗留问题
将upperLeft
和lowerRight
函数的返回类型改为const Point&
,可解决上述问题,使客户只能读取矩形的点,不能修改,增强了const
成员函数的有效性,同时这也是对封装的有限放松:
class Rectangle {
public:
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
};
但即便如此,返回内部构件句柄仍存在风险,如可能产生空悬句柄。以获取 GUI 对象边界框函数为例:
class GUIObject {};
const Rectangle boundingBox(const GUIObject& obj);
GUIObject *pgo;
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());
boundingBox
返回的临时Rectangle
对象(设为temp
)在语句结束时被销毁,其内部Point
对象也随之析构,导致pUpperLeft
成为空悬指针,指向已不存在的对象。
4. 总结与建议
虽然在某些特殊情况下(如string
和vector
的operator[]
)需要返回句柄,但一般应避免返回对象内部构件的句柄(引用、指针或迭代器)。这样做有助于提高对象封装性,确保const
成员函数真正发挥const
效果,并最大程度降低空悬句柄产生的可能性。