作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何东西。
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是抽象的 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
有时,声明一个除纯虚函数外什么也不包含的类很有用。这样的类叫协议类(Protocol class),它为派生类仅提供函数接口,完全没有实现。协议类在条款34中介绍过,并将在条款43再次提及。
声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。
基类为子类提供缺省行为、同时只是在子类想要的时候才给它们的实现:切断虚函数的接口和它的缺省实现之间的联系
方法一:
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飞往某一目的地的代码 }
声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。当一个成员函数为非虚函数时,它在派生类中的行为就不应该不同。实际上,非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为 ---- 不管一个派生类有多特殊。
条款36: 区分接口继承和实现继承