Effective C++ (E3 34、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函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值