C++箴言:制止返回工具内部构件的句柄
副标题#e#
假设你正在一个包括矩形的应用措施上事情。每一个矩形都可以用它的左上角和右下角暗示出来。为了将一个 Rectangle 工具保持在较小状态,你大概抉择那些点的界说的域不该该包括在 Rectangle 自己之中,更符合的做法是放在一个由 Rectangle 指向的帮助的布局体中:
class Point {
// class for representing points
public:
Point(int x, int y);
...
void setX(int newVal);
void setY(int newVal);
...
};
struct RectData {
// Point data for a Rectangle
Point ulhc; // ulhc = " upper left-hand corner"
Point lrhc; // lrhc = " lower right-hand corner"
};
class Rectangle {
...
private:
std::tr1::shared_ptr pData; // see Item 13 for info on
}; // tr1::shared_ptr
由于 Rectangle 的客户需要有本领操控 Rectangle 的区域,因此类提供了 upperLeft 和 lowerRight 函数。但是,Point 是一个用户界说范例,所以,在典范环境下,以传引用的方法通报用户界说范例比传值的方法越发高效的概念,这些函数返回引向底层 Point 工具的引用:
class Rectangle {
public:
...
Point& upperLeft() const { return pData->ulhc; }
Point& lowerRight() const { return pData->lrhc; }
...
};
这个设计可以编译,但它是错误的。实际上,它是自相抵牾的。一方面,upperLeft 和 lowerRight 是被声明为 const 的成员函数,因为它们被设计成仅仅给客户提供一个得到 Rectangle 的点的要领,而不答允客户改变这个 Rectangle。另一方面,两个函数都返回引向私有的内部数据的引用——挪用者可以操作这些引用修改内部数据!譬喻: Point coord1(0, 0);
Point coord2(100, 100);
>
const Rectangle rec(coord1, coord2); // rec is a const rectangle from
// (0, 0) to (100, 100)
rec.upperLeft().setX(50); // now rec goes from
// (50, 0) to (100, 100)!
请留意这里,upperLeft 的挪用者是奈何操作返回的 rec 的内部 Point 数据成员的引用来改变这个成员的。可是 rec 却被期望为 const!
这直接引出两条履历。第一,一个数据成员被封装,可是具有最高可会见级此外函数照旧可以或许返回引向它的引用。在当前环境下,固然 ulhc 和 lrhc 被声明为 private,它们照旧被有效地果真了,因为 public 函数 upperLeft 和 lowerRight 返回了引向它们的引用。第二,假如一个 const 成员函数返回一个引用,引向一个与某个工具有关并存储在这个工具自己之外的数据,这个函数的挪用者就可以改变谁人数据(这正是二进制位常量性的范围性的一个副浸染)。
#p#副标题#e#
我们前面做的每件事都涉及到成员函数返回的引用,可是,假如它们返回指针可能迭代器,因为同样的原因也会存在同样的问题。引用,指针,和迭代器都是句柄(handle)(持有其它工具的要领),而返回一个工具内部构件的句柄老是面对危及工具封装安详的风险。就像我们看到的,它同时还能导致 const 成员函数改变了一个工具的状态。
我们凡是认为一个工具的“内部构件”就是它的数据成员,可是不能被通例地果真会见的成员函数(也就是说,它是 protected 或 private 的)也是工具内部构件的一部门。同样地,不要返回它们的句柄也很重要。这就意味着你毫不该该有一个成员函数返回一个指向拥有较小的可会见级此外成员函数的指针。假如你这样做了,它的可会见级别就会与谁人拥有较大的可会见级此外函数沟通,因为客户可以或许获得指向这个拥有较小的可会见级此外函数的指针,然后就可以通过这个指针挪用这个函数。
无论如何,返回指向成员函数的指针的函数是可贵一见的,所以让我们把留意力返回到 Rectangle 类和它的 upperLeft 和 lowerRight 成员函数。我们在这些函数中挑出来的问题都只需简朴地将 const 用于它们的返回范例就可以解除:
class Rectangle {
public:
...
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
...
};
通过这个修改的设计,客户可以读取界说一个矩形的 Points,但他们不能写它们。这就意味着将 upperLeft 和 upperRight 声明为 const 不再是一句废话,因为他们不再答允挪用者改变工具的状态。至于封装的问题,我们老是存心让客户看到做成一个 Rectangle 的 Points,所以这是封装的一个存心的放松之处。更重要的,它是一个有限的放松:只有读会见是被这些函数答允的,写会见依然被克制。
#p#分页标题#e#
固然如此,upperLeft 和 lowerRight 仍然返回一个工具内部构件的句柄,而这有大概造成其它方面的问题。出格是,这会导致空悬句柄:引用了不再存在的工具的构件的句柄。这种消失的工具的最普通的来历就是函数返回值。譬喻,思量一个函数,返回在一个矩形窗体中的 GUI 工具的 bounding box:
class GUIObject { ... };
const Rectangle // returns a rectangle by
boundingBox(const GUIObject& obj); // value; see Item 3 for why
// return type is const
此刻,思量客户大概会这样利用这个函数:
GUIObject *pgo; // make pgo point to
... // some GUIObject
const Point *pUpperLeft = // get a ptr to the upper
&(boundingBox(*pgo).upperLeft()); // left point of its
// bounding box
对 boundingBox 的挪用会返回一个新建的姑且的 Rectangle 工具。这个工具没有名字,所以我们就称它为 temp。于是 upperLeft 就在 temp 上被挪用,这个挪用返回一个引向 temp 的一个内部构件的引用,出格是,它是由 Points 组成的。随后 pUpperLeft 指向这个 Point 工具。到此为止,一切正常,可是我们无法继承了,因为在这个语句的末端,boundingBox 的返回值—— temp ——被销毁了,这将间接导致 temp 的 Points 的析构。接下来,剩下 pUpperLeft 指向一个已经不再存在的工具;pUpperLeft 空悬在建设它的语句的末端!
这就是为什么任何返回一个工具的内部构件的句柄的函数都是危险的。它与谁人句柄是指针,引用,照旧迭代器没什么干系。它与是否受到 cosnt 的限制没什么干系。它与谁人成员函数返回的句柄自己是否是 const 没什么干系。全部的问题在于一个句柄被返回了,因为一旦这样做了,你就面对着这个句柄比它引用的工具更长命的风险。
这并不料味着你永远不该该让一个成员函数返回一个句柄。有时你必需如此。譬喻,operator[] 答允你从 string 和 vector 中取出单独的元素,而这些 operator[]s 就是通过返回引向容器中的数据的引用来事情的——当容器自己被销毁,数据也将销毁。尽量如此,这样的函数属于特例,而不是老例。
Things to Remember
·制止返回工具内部构件的句柄(引用,指针,或迭代器)。这样会提高封装性,辅佐 const 成员函数发生 cosnt 结果,并将空悬句柄发生的大概性降到最低。