《Effective C++》读书笔记 条款34:区分接口继承和实现继承

第一次记录读书笔记,因为觉得纯读很没有意思,于是突然决定开始写博客记录一下,理解的也不深。菜鸟也希望能有进步,希望自己坚持,前面的条款在后来会补。
这一条款主要讲纯虚函数、虚函数和普通成员函数在public继承时不同的地方。public继承的概念包括两个:函数的接口继承和函数的实现继承。类中的成员函数有三种类型,纯虚函数,虚函数、普通成员函数,这三种类型在public继承时的不同如下表
|纯虚函数 |继承函数接口 |
|虚函数 |函数接口和缺省实现|
|普通成员函数 |接口和强制性实现 |
在进行类的设计时,如果希望派生类只继承成员函数的接口(也就是声明),则该成员函数声明为纯虚函数;
如果希望派生类同时成员函数的继承接口和实现,但又希望能够覆写它们所继承的实现,则该成员函数声明为虚函数;如果希望同时继承成员函数的接口和实现,并且不允许覆写任何东西,则改成员函数声明为普通成员函数即可。

对于这三种函数的不同可以通过例子来说明,首先说明一点,成员函数的接口总是会被继承的(因为public继承意味着is-a)

class shape
{
public:
	virtual void draw() const= 0; //pure virtual
	virtual void error(const std::string& msg);//impure virtual;
	int objectID()const;//non_virtual
//Private:
	//.......
};

class RectTangle :public shape
{
public:
	void draw();//对于具象的派生类,一定要重新声明基类的纯虚函数,否则该派生类也是抽象类
};
void RectTangle::draw()
{

}
class Circle :public shape
{
public:
	void draw();

};
void Circle::draw()
{

}

首先看基类shape,声明了三个函数,draw,error,objectID,分别是pure virtual ,impure virtual,non_virtual。

shape::draw函数说明,形状是画出来的,但是不同的形状有不同的画法,所以不能提供缺省的实现,这就是告诉所有的具象派生类,“你必须提供一个draw函数,但我不干涉你怎么实现”。总的来说,基类设计纯虚函数的目的就是告诉具象派生类一定会具有的功能,但功能的具体实现细则不一样,所以不能给出统一的代码,这就像鸟都会叫,但是叫的方式不一样,对于鸟这个基类,就可以设计一个表示叫的纯虚函数,然后对于不同的鸟类,如乌鸦、喜鹊有不同的实现方式。这就是public继承中的接口继承。
关于纯虚函数书中还提到了一点,我们可以给纯虚函数提供一份实现代码,但调用它的唯一途径是“调用时明确指出其class名称”。举例如下:
*Shape ps1 = new Rectangle;
ps1->Shape::draw();

shape::error,表示每个派生类都必须支持一个“当遇上错误时可调用”的函数,但每个类可自由处理错误,如果某个类不想对错误做出任何特殊的处理行为,他可以回退到基类提供的缺省的错误处理行为。比如动物都要吃东西,基类动物可以提供一个虚函数eat,并提供缺省的吃的方式,但是每一种动物吃的具体方式可能有差异,如直接吞的,嚼碎再吞的等等(举例子好辛苦,总感觉哪里怪怪的,(╥╯^╰╥),举例能力太差了),如果没有特别的吃的方式,就直接使用基类吃的方式。
书上提到用虚函数使基类继承接口和缺省的实现可能会带来错误。用飞机举例,如果将飞机飞行的方式写成虚函数,并提供缺省的代码,避免代码重复,但是却可能出现问题,如果将来我有设计了一个派生类,这种飞机的飞行方式比较特别,而我又忘了覆写飞行方式函数,那么原则上来说生产的飞机可能就飞不起来了,造成故障,就像是你一直骑电瓶车,没学过开车,让你去开车,你用骑电拼车的方式怎么能把车开走呢。那你肯呢个说把fly改成纯虚函数不就好了,但是,如果大部分飞机飞行方式都一样,这种设计方式会造成代码大量重复。那么应该怎么解决这个问题呢?
解决方式:1.是切断“virtual函数的接口”和“缺省实现”之间的连接。也是将fly(飞行函数)定义成纯虚函数,将实现函数用一个而普通成员函数代替(protected),类似这种

class Airplane{
public:
	virtual void fly(const Airport& destination) = 0;
protected://注意是protected
	void defaultFly(const Airport& destination);
};
void Airplane::defaultFly(const Airport& destination)
{
	//缺省行为
}

然后派生类在实现fly函数时可以调用基类的保护成员函数defaultFly,这样既避免可代码重复,也避免了继续设计派生类时忘记提供fly函数
2.将其声明为纯虚函数,并为该纯虚函数提供一份实现代码是(缺省的实现方式),在每个派生类中覆写该纯虚函数,如果不需要特殊的行为,则在该覆写的函数中调用基类的纯虚函数,注意上面说的纯虚函数的调用方式

class Airplane
{
public:
	virtual void fly(const Airport& destination) = 0;
};
void Airplane::fly(const Airport& destination)
{
	//缺省的行为
}

class ModelA :public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		Airplane::fly(destination);
	}
};

这样既避免可代码重复,也避免了继续设计派生类时忘记提供fly函数,而且减少了函数的命名,避免class命名空间污染

shape::objectID()函数,普通成员函数意味着在派生类中不想有不同的行为,派生类直接继承接口和实现

请记住:
1.接口继承和实现继承不同。在public继承下,派生类总是继承基类的接口
2.pure virtual函数只具体指定接口继承
3.impure virtual函数具体指定接口继承及缺省实现继承
3.non_virtual 函数具体指定接口继承以及强制性实现继承

猜你喜欢

转载自blog.csdn.net/junya_zhang/article/details/83414124
今日推荐