考虑以下类:
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
是一个抽象类,它的纯虚函数draw
使它成为一个抽象类.所以客户不能创建Shape class的实体。Shape class声明了三个函数,首先考虑纯虚函数draw:
virtual void draw() const = 0;
由于是纯虚函数,派生类必须重新声明也就是说派生类必须提供一个draw函数,不管你怎么实现它。
但是我们也可以给纯虚函数提供定义,调用它的时候明确指出其class名称:
Shape * p = new Rectangle;
p->Shape::draw();
一般而言这个性质用途有限,我们可以使用非纯虚函数提供更平常和更安全的默认实现,比如error:
virtual void error(const std::string & msg);
这个接口表示,每个类都必须支持一个当遇上错误时可调用的函数,也就是说,你必须支持一个error函数,但如果你不想写,可使用基类的默认版本.
但是允许非纯虚函数同时指定函数声明和函数默认行为,有可能造成危险。考虑以下的类:
class Airport{...};
class Airplane
{
public:
virtual void fly(const Airport & destination);
...
};
class ModelA : public Airplane{...};
class ModelB :public Airplane{...};
现在假设又有了一个新式的C型飞机。但它们的飞行方式不同。程序员在继承体系中针对C型飞机添加了一个类,但由于他们急着让新飞机上线服务,竟然忘了重新定义fly函数:
class ModelC:public Airplane{...};
然后代码中有以下的动作:
Airplane * p = new ModelC;
p->fly(...);
这是一个大问题,这个程序试图以ModelA或ModelB的飞行方式来飞ModelC。
解决这个问题的办法是:切断虚函数接口和默认实现之间的连接,把fly声明为纯虚函数.
virtual void fly(const Airport & destination) = 0;
protected:
void defaultFly(...);
fly以及被改为纯虚函数,只提供飞行接口。若想要使用默认实现,可以在fly函数中对defaultFly
做一个inline
调用.
有些人反对以不同的函数分别提供接口和默认实现,像上面的fly
和defaultFly
那样。这个矛盾这么解决呢?
解决办法也很简单:
virtual void fly(const Airport & destination) = 0;
void Airplane::fly(const Airport & destination)
{
...
}
我们为纯虚函数提供一个实现,用纯虚函数替换了独立函数defaultFly
.
总结:
1.接口继承和实现继承不同,在public继承下,派生类总是继承基类的接口.
2.纯虚函数只具体指定接口继承.
3.非纯虚函数具体指定接口和默认实现继承.
4.非虚函数具体指定接口继承和强制性实现继承.