设计类(class)的时候,你可能会干下面这几件事情:
1.只让继承类(derived class)继承成员函数的接口;
2.让derived class同时继承函数的接口和实现,但又能覆写所继承的实现;
3.同时继承函数的接口和实现,但不允许覆写任何东西;
以下面的类为例,来解释上面的三种实现:
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{...};
对于draw函数,其实我们是可以调用的:如下:
Shape *ps = new Shpae; //Error! Shape is abstract Shape *ps1 = new Rectangle; //ok ps1->draw(); //call Rectangle::draw Shape *ps2 = new Ellipse; //ok ps2->draw(); //Ellipse::draw ps1->Shape::draw(); //Shape::draw ps2->Shpae::draw(); //Shape::draw
对于Shape::error这个函数,为非纯虚函数,他告诉继承类,你如果不想自己写一个,则会调用基类的缺省版本。
这种允许非纯虚函数的双面性行为,可能造成危险。例如,如果有个航空公司,该公司有A型和B型两种飞机,两者以相同的方式飞行,设计继承体系如下:
class Airport {...}; class Airplane { public: virtual void fly(const Airport &destination); ... }; void Airplane::fly(const Airport &destination) { //fly to the destination. } class ModelA: public Airplane {...}; class ModelB: public Airplane {...};
现在,假设该公司购买了一种新式飞机C,飞行模式有些不同,该公司的程序员在继承体系中针对这种飞机添加了一个class,假如他们忘记了定义其fly函数:
class ModelC: public Airplane { ...//not implementing fly() };
调用的时候,你会采用如下的操作:
Airport BCIA(...); //Beijing Capital International Airport; Airplane *pa = new ModelC; ... pa->fly(BCIA); //call Airplane::fly
上面的程序试图以ModelA和ModelB的飞行方式来飞ModelC,用喷气式飞机的方式来飞螺旋桨飞机,要出人命啊。
问题不在于Airplance::fly有缺省行为,而在于ModelC在未说明“我要”的情况下就继承了该缺省行为。
我们可以避免这种做法:
class Airplane { public: virtual void fly(const Airport &destination) = 0; ... protected: void defaultFly(const Airport &destination); }; void Airplane::defaultFly(const Airport &destination) { //implementation of flying. }
将Airplane::fly改为一个pure virtual函数,这样的话,每个继承类都要自己实现fly:
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函数来,嘿嘿:
class ModelC: public Airplane { virtual void fly(const Airport &destination); ... }; void ModelC::fly(const Airport &destination) { //ModelC's way. }
这种方式可能因为使用者的复制粘贴而错误,但是比之前有了保障。至于Airplane::defaultFly,现在设置成了protected,因为他是继承类的实现方式,用户只在意飞机能不能飞,不在意怎么飞的。
当然,现在这种方式也有个问题,多定义了一个defaultFly,可能会导致命名空间污染的问题。我们可以用下面这种方式,就是纯虚函数必须在继承类中重新声明,但是他们可以拥有自己的实现。例如:
class Airplane { public: virtual void fly(const Airport &destination) = 0; }; void Airplane::fly(const Airport &destination) { //do flying; } 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) { //do ModelC's flying; }
最后,对于objectID这个非虚函数,意味着在继承类中,不需要对其进行修改。所以在继承类中不建议重新定义。这个会在下个议题中阐述。
来源:https://www.cnblogs.com/edmundli/p/4444538.html