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

 

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的实例上调用setWidthsetHeight,就出现问题了。

 

总结:

在确定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(转调函数)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章