避免返回handles指向对象内部成员
示例代码如下:
class Point
{
public:
Point(int x, int y);
...
void SetX(int x);
void SetY(int y);
...
};
struct RectData
{
Point ulhc;
Point lrhc;
};
class Rectangle
{
public:
Point& upperLeft() const { return pData->ulhc;}
Point& lowerRight() const { return pData->lrhc;}
...
private:
std::shared_ptr<RectData> pData;
}
调用如下:
Point coord1(1, 1);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);
rec.upperLeft().SetX(50); // 标识1
标识1处的代码编译没有问题,但是语义上却有矛盾。rec为一个常量,却可以通过标识1处修改其内部的值。
这带给我们两个教训:
- 成员变量的封装性最多只等于"返回其reference"的函数的访问级别。
- 如果const成员传出一个reference,后者所指数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。
Reference、指针和迭代器统统都是所谓的handles(用来取得某个对象),而返回一个"代表对象内部数据"的handle,随之而来的便是"降低对象封装性"的风险。它也可能导致"虽然调用const成员函数却造成对象状态被改变"。
修改比较简单,返回引用时加上const。
class Rectangle
{
public:
...
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const {return pData->lrhc; }
}
即便如此,upperLeft和lowerRight还是返回了"代表对象内部"的handles,有可能在其他场合带来问题。它可能导致dangling handles,这种handles所指东西(的所属对象)不复存在的对象。最常见的来源就是函数返回值。
示例如下:
class GUIObject { ... }
const Rectangle boundingBox(const GUIObject& obj);
使用如下:
GUIObject* pgo;
...
const Poin* pUpperLeft = &(boundingBox(*pgo).upperLeft()); // 标识2
标识2处执行完后,pUpperLeft即为一个悬挂指针,其指向的临时对象已经析构。
这就是为什么函数如果返回一个handle代表对象内部成分总是危险的原因。不论这所谓的handle是个指针或迭代器或reference,也不论这个handle是否为const,也无论那个返回handle的成员函数是否为const。这里唯一的关键是,有个handle被传出来了,一旦如此你就是暴露在"handle比其所指对象更长寿"的风险下。
这并不意味你绝对不可以让成员函数返回handle。有时候你必须那么做。例如operator[]就允许你"摘采"strings和vectors个别元素。而这些opreator[]s就是返回references指向"容器内的数据",那些数据会随着容器被销毁而销毁。尽管如此,这样的函数毕竟是例外,不是常态。
请记住:
避免返回handles(包括refercence、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生dangling handles的可能性降到最低。