当类的成员函数为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函数。