Effective-cpp-#28

Avoid returning "handles" to object internals

动机

先来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point{...};

struct RectData{
Point ulhc;
Point lrhc;
};

class Rectangle{
private:
std::trl::shared_ptr<RectData> pData;
public:
Point& upperLeft() const{return pData->ulhc;}
Point& lowerRight() const {return pData->lhrc;}
};

使用:

1
2
const Rectangle rec(point1, point2);
rec.upperLeft().setX(50);

其实,这样使用编译器是不会报错的,但其出现了矛盾的情况,原因是我们在该函数后面指定了const,则希望rec是不变的。但我们可以看到,由于我们返回的是一个引用,则可以通过引用去修改对象内部的成员。
同理,返回指针或者迭代器这些handles,也会出现这种情况。

解决方法

为了解决这个问题,我们可以这样:

1
2
3
4
5
class Rectangle{
...
const Point& upperLeft() const{return pData->ulhc;}
const Point& lowerRight() const {return pData->lhrc;}
};
通过这种做法,用户可以读取对象的points,但不能修改它们。

但这种用法同样会有一些问题,那就是有可能会出现dangling handles

1
2
3
4
5
6
class GUIObj{...};
const Rectangle boudingBox(const GUIObj& obj);

GUIObj* obj;
...
const Point* p = &(boudingBox(*p).upperLeft());
这里的问题是,由于这里够早的是一个临时Rectangle对象,也就是说,当这个语句结束时候临时对象就会被销毁,那么p所指向的Point也会被析构。也就是会发生dangling handles

这是因为返回一个handle代表对象内部成员的话,不管这个handle是指针还是引用,也不管是不是const,这里的关键是一旦这个handle传出去了,你就得承受一个风险——handle可能会比其所指向的对象有着更长的声明周期。

建议

  • 避免返回任何handles指向对象的内部。另外,还要使得const成员函数的行为像个const,并且尽量避免发生dangling handles