您的位置:首页 > 编程语言 > C语言/C++

Effective C++笔记: 继承和面向对象设计(一)

2009-07-17 17:57 351 查看
 

Item 32: 确保你的public inheritance 塑模出 "is-a"关系
如果你写了一个 class D ("Derived") 从 class B ("Base") 公开继承,你就是在告诉 C++ 编译器(以及你的代码的读者)每一个类型为 D 的对象也是一个类型为 B 的对象,但是反之则不然。你就是在说 B 描绘了一个比 D 更一般的概念,D 描述了一个比 B 更特殊的概念。你就是在声称一个类型为 B 的对象可以使用的任何地方,一个类型为 D 的对象一样可以使用,因为每一个类型为 D 的对象也就是一个类型为 B 的对象。另一方面,如果你需要一个类型为 D 的对象,一个类型为 B 的对象则不行:每一个 D 都是一个 B,但是反之则不然。
public inheritance 断言,适用于 base class objects(基类对象)的每一件事——每一件事!——也适用于 derived class objects(派生类对象)。在我们考虑继承关系的时候,必须考虑这一点。比较典型的是矩形和正方形,通常我们会直觉的认为正方形应该从矩形继承。但问题出来了:
考虑如下代码:
class Rectangle {
public:
  virtual void setHeight(int newHeight);
  virtual void setWidth(int newWidth);
  virtual int height() const;               // return current values
  virtual int width() const;
  ...
};
void makeBigger(Rectangle& r)               // function to increase r's area
{
  int oldHeight = r.height();
  r.setWidth(r.width() + 10);               // add 10 to r's width
  assert(r.height() == oldHeight);          // assert that r's
}                                           // height is unchanged
很清楚,断言应该永远不会失败。makeBigger 仅仅改变了 r 的宽度,它的高度始终没有变化。
现在,考虑以下代码,使用 public inheritance 使得 squares 可以像 rectangles 一样进行处理:
class Square: public Rectangle {...};
Square s;
...
assert(s.width() == s.height());           // this must be true for all squares
makeBigger(s);              // by inheritance, s is-a Rectangle,
                           // so we can increase its area
assert(s.width() == s.height());           // this must still be true
                                           // for all squares
和刚才那个一样明显,第二个断言也应该永远不会失败。根据定义,正方形的宽度和高度是相等的。
现在,如果在square的实例上调用setWidth或setHeight,就出现问题了。
 
总结:
在确定public继承之前,请确定适用于base class的每一件事也一定适用于derived class,因为每一个derived class对象也都是一个base class对象。
 
Item 33: 避免覆盖(hiding)通过继承而来的名称
编译器的搜索顺序:假设有以下代码
class Base {
private:
  int x;
public:
  virtual void mf1() = 0;
  virtual void mf2();
  void mf3();
  ...
};
class Derived: public Base {
public:
  virtual void mf1();
  void mf4();
  ...
};
 
void Derived::mf4()
{
  ...
  mf2();
  ...
}
当编译器看到一个名为mf2的使用,它就必须断定它指涉什么。它通过搜索名为 mf2 的某物的定义的作用域来做这件事。首先它在 local 作用域中搜索(也就是 mf4 的作用域),但是它没有找到被称为 mf2 的任何东西的声明。然后它搜索它的包含作用域,也就是 class Derived 的作用域。它依然没有找到叫做 mf2 的任何东西,所以它上移到它的上一层包含作用域,也就是 base class 的作用域。在那里它找到了名为 mf2 的东西,所以搜索停止。如果在 Base 中没有 mf2,搜索还会继续,首先是包含 Base 的 namespace(s)(如果有的话),最后是 global 作用域。
 
因此,Derived class中的同名成员会覆盖base class中的成员,包括变量或函数。对成员函数来说,被覆盖与否只与函数名相关,与函数参数,函数类型(pure virtual, virtual, non-virtual)等无关。
class Base {
private:
  int x;
public:
  virtual void mf1() = 0;
  virtual void mf1(int);
  virtual void mf2();
  void mf3();
  void mf3(double);
  ...
};
class Derived: public Base {
public:
  virtual void mf1();
  void mf3();
  void mf4();
  ...
};
base class 中的所有名为 mf1 和 mf3 的函数被 derived class 中的名为 mf1 和 mf3 的函数覆盖。从名字搜索的观点看,Base::mf1 和 Base::mf3 不再被 Derived 继承!
Derived d;
int x;
...
d.mf1();                   // fine, calls Derived::mf1
d.mf1(x);                  // error! Derived::mf1 hides Base::mf1
d.mf2();                   // fine, calls Base::mf2
d.mf3();                   // fine, calls Derived::mf3
d.mf3(x);                  // error! Derived::mf3 hides Base::mf3
就像你看到的,即使 base 和 derived classes 中的函数具有不同的参数类型,它也同样适用,而且不管函数是 virtual 还是 non-virtual,它也同样适用。覆盖只与名字相关!
 
解决覆盖的办法:
1.使用using声明式
class Derived: public Base {
public:
  using Base::mf1;       // make all things in Base named mf1 and mf3
  using Base::mf3;       // visible (and public) in Derived's scope
  virtual void mf1();
  void mf3();
  void mf4();
  ...
};
现在 inheritance 就可以起到预期的作用:
Derived d;
int x;
...
d.mf1();                 // still fine, still calls Derived::mf1
d.mf1(x);                // now okay, calls Base::mf1
d.mf2();                 // still fine, still calls Base::mf2
d.mf3();                 // fine, calls Derived::mf3
d.mf3(x);                // now okay, calls Base::mf3
这意味着如果你从一个带有重载函数的 base class 继承,而且你只想重定义或替换它们中的一部分,你需要为每一个你不想覆盖的名字使用 using declaration。如果你不这样做,一些你希望继承下来的名字会被覆盖。
3.使用转交函数: (该方法通常适用于private继承,因为对public继承来说,任何一件能对base class做的事情也应该可以对derived class生效!!参见条款32)
class Base {
public:
  virtual void mf1() = 0;
  virtual void mf1(int);
  ...                                    // as before
};
class Derived: private Base {
public:
  virtual void mf1()                   // forwarding function; implicitly
  { Base::mf1(); }                     // inline (see Item 30)
  ...
};
...
Derived d;
int x;
d.mf1();                               // fine, calls Derived::mf1
d.mf1(x);                              // error! Base::mf1() is hidden
总结:
derived classes 中的名字覆盖 base classes 中的名字,在 public inheritance 中,这从来不是想要的。
为了使隐藏的名字重新可见,使用 using declarations 或者 forwarding functions(转调函数)。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息