C++箴言:避免返回对象内部构件的句柄

2016-02-19 12:18 2 1 收藏

下面图老师小编要跟大家分享C++箴言:避免返回对象内部构件的句柄,简单的过程中其实暗藏玄机,还是要细心学习,喜欢还请记得收藏哦!

【 tulaoshi.com - 编程语言 】

假设你正在一个包含矩形的应用程序上工作。每一个矩形都可以用它的左上角和右下角表示出来。为了将一个 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_ptrRectData 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 成员函数返回一个引用,引向一个与某个对象有关并存储在这个对象本身之外的数据,这个函数的调用者就可以改变那个数据(这正是二进制位常量性的局限性的一个副作用)。

  我们前面做的每件事都涉及到成员函数返回的引用,但是,如果它们返回指针或者迭代器,因为同样的原因也会存在同样的问题。引用,指针,和迭代器都是句柄(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,所以这是封装的一个故意的放松之处。更重要的,它是一个有限的放松:只有读访问是被这些函数允许的,写访问依然被禁止。

  虽然如此,upperLeft 和 lowerRight 仍然返回一个对象内部构件的句柄,而这有可能造成其它方面的问题。特别是,这会导致空悬句柄:引用了不再存在的对象的构件的句柄。这种消失的对象的最普通的

来源:https://www.tulaoshi.com/n/20160219/1600531.html

延伸阅读
在 C++ 中,就像其它面向对象编程语言,可以通过定义一个新的类来定义一个新的类型。作为一个 C++ 开发者,你的大量时间就这样花费在增大你的类型系统。这意味着你不仅仅是一个类的设计者,而且是一个类型的设计者。重载函数和运算符,控制内存分配和回收,定义对象的初始化和终结过程——这些全在你的掌控之中。因此你应该在类设计中倾注...
很多企业在发展过程中,都有过几套治理软件共用的经历,往往造成在企业内部形成信息孤岛,无法统一治理的窘境 !-- frame contents -- !-- /frame contents -- 中冀集团自几年前开始信息化尝试之后,一共使用了4套系统,对梳理企业治理流程功劳不小,本来不必再使用集成化企业治理软件,但几套系统各自为政造成了集团内部的信息孤岛...
我谈到让一个类支持隐式类型转换通常是一个不好的主意。当然,这条规则有一些例外,最普通的一种就是在创建数值类型时。例如,假如你设计一个用来表现有理数的类,答应从整数到有理数的隐式转换看上去并非不合理。这的确不比 C++ 的内建类型从 int 到 double 的转换更不合理(而且比 C++ 的内建类型从 double 到 int 的转换合理得多)。在...
看如下代码: 代码如下: #includeiostream class TestConstructor { public:     TestConstructor()     {         std::cout"TestConstructor()"std::endl;     }     ~TestConstructor()     {     &...
介绍 多态是一种威力强大的设计机制,答应你继续一个抽象的public接口之后,封装相关的类型,需要付出的代价就是额外的间接性--不论是在内存的获得,或是在类的决断上,C++通过class的pointer和references来支持多态,这种程序风格就称为"面向对象". 大家好,雷神关于《深度探索C++对象模型》笔记终于又和大家见面了,速度慢的真是可以。...

经验教程

364

收藏

13
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部