34. Differentiate between inheritance of interface and inheritance ofimplementations

本文探讨了C++中接口继承和实现继承的区别,通过pure virtual、impure virtual和non-virtual函数,精确控制基类和派生类之间的继承关系。

区分接口继承和实现继承

有如下继承体系:

class Shape
{
publicvirtual void draw() const = 0; // pure virtual function
	virtual void error(const std::string& msg); // impure virtual function
	int objectID() const; // non-virtual function
};

class Rectangle : public Shape {...};
class Ellipse : public Shape {...};

声明pure virtual函数的目的是为了让derived classes只继承函数接口。

这对Shape::draw()非常合理,因为所有的Shape对象都应该是可以绘出的。但是Shape class无法为此函数提供合理的缺省实现,比较椭圆和矩形的绘制迥然不同。Shape::draw的声明式是对其继承类说:你必须提供一个draw函数,但我不干涉这么实现它。

impure virtual函数背后的故事和pure virtual函数有点不同。derived classes继承其函数接口,但impure virtual函数会提供一份代码,derived classes可能覆写(override)它。

声明impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。

对于error函数,该接口表示,每个class都必须支持一个"当遇上错误是可调用的"函数,但每个class可自由处理错误。如果某个class不想针对错误给出其特殊行为,它可以退回到Shape class提供的缺省错误处理行为,也就是说Shape::error的声明式高速derived classes的设计者,“你必须支持一个error函数,如果你不想自己写一个,也可以使用Shape class提供的缺省行为”。

但是,允许impure virtual函数同时指定函数声明和函数缺省行为,却可能造成危险。
考虑一下继承体系:

class Airport{...};
class Airplane
{
public:
	virual void fly(const Airport& destination);
	...
};


void Airplane::fly() 
{	
	// 缺省代码,将飞机飞至指定目的地
}

class ModelA : public Airplane{...};
class ModelB : public Airplane{...};

ModelA、ModelB继承了AirPlane的fly,同时继承其默认实现。
现在需要添加一新类ModelC,它的fly函数需要重新定义,但是忘记重新定义其fly函数:

class ModelC : public Airplane
{
	...
};

这将酿成大错,调用ModelC的fly函数将会和ModelA、ModelB中的fly一样,而没有展示出自己的不同。
问题不在于AirPlane有缺省行为,而在于ModelC在未明白说出"我要"的情况下即就继承了该缺省行为。

下面是一种改进办法:它切断"virtual函数接口"和其"缺省实现"之间的连接。

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

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实现函数,因为AirPlane中的pure virtual函数迫使ModelC必须提供自己的fly版本:

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

void ModelC::fly(const Airport& destination)
{
	将C型飞机飞至指定的目的地
}

有些人会返回以不同的函数分别提供接口和缺省实现,像上述的fly和defaultFly那样。他们关心因过度雷同的函数名称而引起的class命名空间污染问题。可以利用"pure virtual函数必须在derived classes中重新声明,但它们也可以拥有自己的实现"这一事实。下面是实现:

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);
	}
};

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)
	{
		Airplane:fly(destination);
	}
};

void ModelC::fly(const Airport& destination)
{
	将C型飞机飞至指定的目的地
}

这几乎和前一个设计一模一样,只不过pure virtual函数Airplane::fly替换了独立函数Airplane::defaultFly。本质上,现在的fly被分割为两个基本要素:其声明部分表现为接口,其定义部分则表现出缺省行为。如果合并fly和defaultFly,就丧失了"让两个函数有不同保护级别"的机会:习惯上被设为protected的函数(defaultFly)如今成了public。

最后,看下Shape的non-virtual函数objectID:

如果成员函数是个non-virtual函数,意味是它并不打算再derived classes中有不同的行为。实际上一个non-virtual函数所表现的不变性凌驾于特异性,以为它表示不论derived classes变得多么特异化,他的行为都不可以改变,就其自身而言:

声明non-virtual函数的目的是为了令derived classes继承函数的接口即一份强制性实现。

pure virtual函数、impure virtual函数、non-virtual函数之间的差异,使你得以精确指定你想要derived classes继承的东西:只继承接口,或是继承接口和一份缺省实现,或是继承接口和一份强制实现。由于这些不同类型的声明意味根本意义并不相同的事情,当你声明你的成员函数时,必须慎重选择。如果你确实履行,应该能够避免经验不足的class设计这最常犯的两个错误:

  1. 第一个错误时将所有函数声明为non-virtual。
  2. 另一个常见错误是将所有函数声明为virtual。

请记住:

  1. 接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
  2. pure virtual函数值具体指定接口继承。
  3. impure virtual函数具体指定接口继承即缺省实现继承。
  4. non-virtual函数具体指定接口继承以及强制性实现继承。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值