Effective C++ (E3 34、36)笔记之接口继承和实现继承

xiaoxiao2021-02-28  36

当类的成员函数为pure virtual、impure virtual、non-virtual时,派生类对这些函数进行继承其意义是不同的:

pure virtual:只继承接口

impure virtual:继承接口和一份缺省实现

non-virtual:继承类继承其接口以及一份强制性实现,继承类绝不应该重新定义

现在先从impure virtual讨论。有一个类描述飞机,其中有个virtual函数描述"飞行",该函数设计成impure virtual意图是想给飞机的继承类(如某种空客飞机)一份“飞行”行为的缺省实现。

#include<iostream> #include<string> using namespace std; class airport{ public: airport(const std::string& name): sname(name) {} const std::string& name() const{ //const func must return const quote,see csdn collection article return sname; } private: std::string sname; }; class airplane{ public: airplane(const std::string& name): sname(name) {} const std::string& name() const{ return sname; } virtual void fly(const airport& dest){cout<<this->name()<<" fly to "<<dest.name()<<endl;} private: std::string sname; }; 但作为airplane类的继承类,如果忘了定义自己的那份fly而接收了默认的airplane::fly,导致fly行为其实是错的。 如开篇所述,其实问题在于继承impure virtual函数意味着:继承接口和一份缺省实现。而对于继承这份缺省,实现如果客户(继承类)没有明确的意识而稀里糊涂接收,那么这种依赖客户动作的基类 设计也不失为一种隐患。

应该这样:可以提供缺省实现给继承类,但前提是他们明确要求,否则免谈。 该思路就是将接口和缺省实现分开:接口使用pure virtual,缺省实现使用另一个函数(该缺省只能是non virtual,否则又依赖客户而再次掉入上述问题)。

代码大致这样:

class baseA{ public: virtual void print()=0; protected: void defaultprint(){ cout<<"print baseA"<<endl; } }; class derivedA0:public baseA{ public: virtual void print(){ defaultprint(); } }; class derivedA1:public baseA{ public: virtual void print(){ cout<<"print derivedA1"<<endl; } }; A的print行为现在仅仅是个interface,其客户必须提供实现。至于怎么实现,A提供了一个专供继承类用的protected的缺省方案供选择。爱用不用此缺省方法由客户决定。

但是在继承类中,原本protected的defaultprint()在public的print()中得以调用,defaultprint()有降级为public的嫌疑。 为了弥补此缺陷,接口还是使用pure virtual,并为该pure virtual提供份定义作为缺省实现(pure virtual是可以定义的!):

class airplane{ public: airplane(const std::string& name): sname(name) {} const std::string& name() const{ return sname; } virtual void fly(const airport& dest)=0; private: std::string sname; }; void airplane::fly(const airport& dest) { cout<<this->name()<<" fly to "<<dest.name()<<endl; } class boyin:public airplane{ public: boyin(const std::string& name):airplane(name) { } virtual void fly(const airport& dest) { airplane::fly(dest); } }; class kongke:public airplane{ public: kongke(const std::string& name):airplane(name) { } virtual void fly(const airport& dest) { cout<<this->name()<<" fly from "<<dest.name()<<endl; } };

这样处理,基类省去了个缺省函数也没有什么丧失函数保护级别的问题了。最后是验证:

int main(){ airport port0("shanghai"); kongke plane0("kongke_p0"); boyin plane1("boyin_p1"); //airplane pa("dd"); //err, airplane is still a abstract class plane0.fly(port0); //ok, kongke_p0 fly from shanghai plane1.fly(port0); //ok, boyin_p1 fly to shanghai }

综上,对于函数该指定为pure virtual、impure virtual、non-virtual应该有个清晰的认识。 几个宗旨:

任何打算做base的类都应该有若干virtual函数 ,其析构首先就要为virtual。 有的函数不该被重新定义,其不变性凌驾于特异性.应该为non-virtual。

最后引申讨论下non-virtual的继承。

class B{ public: void mf(){ //nonvirtual and D defined==>BD; nonvirtual and D nodefined==>BB;virtual==>DD cout<<"call B:mf"<<endl; } }; class D:public B{ public: void mf(){ cout<<"call D:mf"<<endl; } };

类B、D的都有non-virtual mf(), 使用两个类型分别为B、D的指针来指向D对象,会发生诡异现象:

int main() { D x; B* pb=&x; D* pd=&x; pb->mf(); //call B:mf pd->mf(); //call D:mf return 0; } 明明pb指向D对象(而且D::mf明显把B:mf名称遮掩了),结果却是B::mf。 原因是non-virtual属于静态绑定,pb声明类型是B*,通过pb调用的 non-virtual函数永远是B所定义的版本。 而virtual属于动态绑定,如声明virtual B::mf,则 pb和pd都指向真正的D对象。

除了指针以外、引用也有一样的抽风行径。 public继承是一种“is a”的关系。而non-virtual函数的的不变性凌驾于特异性。无论从哪个观点,继承类都不应该重新定义non-virtual函数。

转载请注明原文地址: https://www.6miu.com/read-1600329.html

最新回复(0)