条款36: 区分接口继承和实现继承

作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何东西。

class Shape {
public:
  virtual void draw() const = 0;

  virtual void error(const string& msg);

  int objectID() const;

  ...

};

class Rectangle: public Shape { ... };

class Ellipse: public Shape { ... };

首先看纯虚函数draw。纯虚函数最显著的特征是:它们必须在继承了它们的任何具体类中重新声明,而且它们在抽象类中往往没有定义。把这两个特征放在一起,就会认识到:

· 定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

为一个纯虚函数提供定义也是可能的。也就是说,你可以为Shape::draw提供实现,C++编译器也不会阻拦,但调用它的唯一方式是通过类名完整地指明是哪个调用:

Shape *ps = new Shape;           // 错误! Shape是抽象的,new会调用类的构造函数,实例化对象,显然不行

Shape *ps1 = new Rectangle;      // 正确
ps1->draw();                     // 调用Rectangle::draw

Shape *ps2 = new Ellipse;        // 正确
ps2->draw();                     // 调用Ellipse::draw

ps1->Shape::draw();              // 调用Shape::draw

ps2->Shape::draw();              // 调用Shape::draw

Shape::draw();           //出错,非静态成员引用必须与特定对象相对

有时,声明一个除纯虚函数外什么也不包含的类很有用。这样的类叫协议类(Protocol class),它为派生类仅提供函数接口,完全没有实现。协议类在条款34中介绍过,并将在条款43再次提及。

声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。

基类为子类提供缺省行为、同时只是在子类想要的时候才给它们的实现:切断虚函数的接口和它的缺省实现之间的联系

1.基类函数声明为纯虚函数,同时缺省实现定义在另一个函数中,若派生类想使用基类的缺省实现,则在继承而来的纯虚函数中显式调用缺省实现函数

2.基类函数声明为纯虚函数,同时基类中为该纯虚函数提供定义,若派生类想使用基类的缺省实现,则在继承而来的纯虚函数中显式调用基类中该函数(类名调用)

方法一:

class Airplane {
public:
  virtual void fly(const Airport& destination) = 0;

  ...

protected:
  void defaultFly(const Airport& destination);
};

void Airplane::defaultFly(const Airport& destination)
{
  飞机飞往某一目的地的缺省代码
}

注意Airplane::fly已经变成了纯虚函数,它提供了飞行的接口。缺省实现还是存在于Airplane类中,但现在它是以一个独立函数(defaultFly)的形式存在的。ModelA和ModelB这些类想执行缺省行为的话,只用简单地在它们的fly函数体中对defaultFly进行一个内联调用

class ModelA: public Airplane {
public:
  virtual void fly(const Airport& destination)
  { defaultFly(destination); }

  ...

};

class ModelB: public Airplane {
public:
  virtual void fly(const Airport& destination)
  { defaultFly(destination); }

  ...

};

对于ModelC类来说,它不可能无意间继承不正确的fly实现。因为Airplane中的纯虚函数强迫ModelC提供它自己版本的fly。

class ModelC: public Airplane {
public:
  virtual void fly(const Airport& destination);
  ...

};

void ModelC::fly(const Airport& destination)
{
  ModelC飞往某一目的地的代码
}

方法二:纯虚函数必须在子类中重新声明,但它还是可以在基类中有自己的实现

class Airplane {
public:
  virtual void fly(const Airport& destination) = 0;

  ...

};

void Airplane::fly(const Airport& destination)
{
  飞机飞往某一目的地的缺省代码
}

class ModelA: public Airplane {
public:
  virtual void fly(const Airport& destination)
  { Airplane::fly(destination); }

  ...

};

class ModelB: public Airplane {
public:
  virtual void fly(const Airport& destination)
  { Airplane::fly(destination); }

  ...

};

class ModelC: public Airplane {
public:
  virtual void fly(const Airport& destination);

  ...

};

void ModelC::fly(const Airport& destination)
{
  ModelC飞往某一目的地的代码
}

声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。当一个成员函数为非虚函数时,它在派生类中的行为就不应该不同。若派生类又重定义了该函数,则会隐藏基类的该函数。实际上,非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为 ---- 不管一个派生类有多特殊。

 

posted @ 2014-08-20 13:57  合唱团abc  阅读(357)  评论(0编辑  收藏  举报