(public) inheritance 这个表面上简单易懂的观念,一旦被近距离审视,就会被证明是由两个相互独立的部分组成的:inheritance of function interfaces(函数接口的继承)和 inheritance of function implementations(函数实现的继承)。这两种 inheritance 之间的差异正好符合本书 Introduction 中论述的 function declarations(函数声明)和 function definitions(函数定义)之间的差异。
作为一个 class 的设计者,有的时候你想要 derived classes 只继承一个 member function 的 interface (declaration)。有的时候你想要 derived classes 既继承 interface(接口)也继承 implementation(实现),但你要允许它们替换他们继承到的 implementation。还有的时候你想要 derived classes 继承一个函数的 interface(接口)和 implementation(实现),而不允许它们替换任何东西。
为了更好地感觉这些选择之间的不同之处,考虑一个在图形应用程序中表示几何图形的 class hierarchy(类继承体系):
class Shape { public: virtual void draw() const = 0;
virtual void error(const std::string& msg);
int objectID() const;
... };
class Rectangle: public Shape { ... };
class Ellipse: public Shape { ... };
Shape 是一个 abstract class(抽象类),它的 pure virtual function(纯虚拟函数)表明了这一点。作为结果,客户不能创建 Shape class 的实例,只能创建从它继承的 classes 的实例。但是,Shape 对所有从它(公有)继承的类施加了非常强大的影响,因为
成员函数 interfaces are always inherited。就像 Item 32 解释的,public inheritance 意味着 is-a,所以对一个 base class 来说成立的任何东西,对于它的 derived classes 也必须成立。因此,如果一个函数适用于一个 class,它也一定适用于它的 derived classes。
Shape class 中声明了三个函数。第一个,draw,在一个明确的显示设备上画出当前对象。第二个,error,如果 member functions 需要报告一个错误,就调用它。第三个,objectID,返回当前对象的唯一整型标识符。每一个函数都用不同的方式声明:draw 是一个 pure virtual function(纯虚拟函数);error 是一个 simple (impure?) virtual function(简单虚拟函数);而 objectID 是一个 non-virtual function(非虚拟函数)。这些不同的声明暗示了什么呢?
考虑第一个 pure virtual function(纯虚拟函数)draw:
class Shape { public: virtual void draw() const = 0; ... };
pure virtual functions(纯虚拟函数)的两个最显著的特性是它们必须被任何继承它们的具体类重新声明,和抽象类中一般没有它们的定义。把这两个特性加在一起,你应该认识到。
声明一个 pure virtual function(纯虚拟函数)的目的是使 derived classes 继承一个函数 interface only。
这就使 Shape::draw function 具有了完整的意义,因为它要求所有的 Shape 对象必须能够画出来是合情合理的,但是 Shape class 本身不能为这个函数提供一个合乎情理的缺省的实现。例如,画一个椭圆的算法和画一个矩形的算法是非常不同的,Shape::draw 的声明告诉具体 derived classes 的设计者:“你必须提供一个 draw function,但是我对于你如何实现它不发表意见。”
顺便提一句,为一个 pure virtual function(纯虚拟函数)提供一个定义是有可能的。也就是说,你可以为 Shape::draw 提供一个实现,而 C++ 也不会抱怨什么,但是调用它的唯一方法是用 class name 限定修饰这个调用:
Shape *ps = new Shape; // error! Shape is abstract
Shape *ps1 = new Rectangle; // fine ps1->draw(); // calls Rectangle::draw
Shape *ps2 = new Ellipse; // fine ps2->draw(); // calls Ellipse::draw
ps1->Shape::draw(); // calls Shape::draw
ps2->Shape::draw(); // calls Shape::draw
除了帮助你在鸡尾酒会上给同行程序员留下印象外,这个特性通常没什么用处,然而,就像下面你将看到的,它能用来作为一个“为 simple (impure) virtual functions 提供一个 safer-than-usual 的实现”的机制。
simple virtual functions 背后的故事和 pure virtuals 有一点不同。derived classes 照常还是继承函数的 interface,但是 simple virtual functions 提供了一个可以被 derived classes 替换的实现。如果你为此考虑一阵儿,你就会认识到
声明一个 simple virtual function 的目的是让 derived classes 继承一个函数 interface as well as a default implementation。
考虑 Shape::error 的情况:
class Shape { public: virtual void error(const std::string& msg); ... };
interface 要求每一个 class 必须支持一个在遭遇到错误时被调用的函数,但是每一个 class 可以自由地用它觉得合适的任何方法处理错误。如果一个 class 不需要做什么特别的事情,它可以仅仅求助于 Shape class 中提供的错误处理的缺省版本。也就是说,Shape::error 的声明告诉 derived classes 的设计者:“你应该支持一个 error function,但如果你不想自己写,你可以求助 Shape class 中的缺省版本。”
结果是:允许 simple virtual functions 既指定一个函数接口又指定一个缺省实现是危险的。来看一下为什么,考虑一个 XYZ 航空公司的飞机的 hierarchy(继承体系)。XYZ 只有两种飞机,Model A 和 Model B,它们都严格地按照同样的方法飞行。于是,XYZ 设计如下 hierarchy(继承体系):
class Airport { ... }; // represents airports
class Airplane { public: virtual void fly(const Airport& destination);
...
};
void Airplane::fly(const Airport& destination) { default code for flying an airplane to the given destination }
class ModelA: public Airplane { ... };
class ModelB: public Airplane { ... };
为了表述所有的飞机必须支持一个 fly 函数,并为了“不同机型可能(在理论上)需要不同的对 fly 的实现”的事实,Airplane::fly 被声明为 virtual。然而,为了避免在 ModelA 和 ModelB classes 中些重复的代码,缺省的飞行行为由 Airplane::fly 的函数体提供,供 ModelA 和 ModelB 继承。
这是一个经典的 object-oriented 设计。因为两个 classes 共享一个通用特性(它们实现 fly 的方法),所以这个通用特性就被转移到一个 base class 之中,并由两个 classes 来继承这个特性。这个设计使得通用特性变得清楚明白,避免了代码重复,提升了未来的可扩展性,简化了长期的维护——因为 object-oriented 技术,所有这些东西都受到很高的追捧。XYZ 航空公司应该引以为荣。
现在,假设 XYZ 公司的财富增长了,决定引进一种新机型,Model C。Model C 在某些方面与 Model A 和 Model B 不同。特别是,它的飞行不同。
XYZ 公司的程序员在 hierarchy(继承体系)中增加了 Model C 的 class,但是由于他们匆匆忙忙地让新的机型投入服务,他们忘记了重定义 fly function:
class ModelC: public Airplane {
... // no fly function is declared };
于是,在他们的代码中,就出现了类似这样的东西:
Airport PDX(...); // PDX is the airport near my home
Airplane *pa = new ModelC;
...
pa->fly(PDX); // calls Airplane::fly!
这是一个灾难:企图让一个 ModelC object 像一个 ModelA 或 ModelB 一样飞行。这在旅行人群中可不是一种鼓舞人心的行为。 [1] [2] 下一页
|