【Effective C++】区分接口继承和实现继承

当你在设计一个 class 的继承体系时,对于基类的成员函数,你可能会希望 Derived class :

  • 只继承基类成员函数的接口
  • 能够同时继承基类成员函数的接口和实现,但是又希望能够 override
  • 能够同时继承基类成员函数的接口和实现,且不允许 override

这三种不同的基类成员函数实现方式,实际上就对应了基类成员函数的三种不同形式:pure virtual function,impure virtual function 和 non-virtual function。假设现在有如下的继承体系,利用这个继承体系对这三种不同的成员函数类型作详细说明。

class Shape
{
public:
	virtual void draw() const = 0;
	virtual void error(const std::string& msg);
	int objectID() const;
}

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

pure virtual function


  • Shape 中拥有一个 pure virtual function draw,这使得 Shape 成为了一个 abstract class,客户端不能够创建 Shape 的实体。
  • pure virtual function 最重要的两个特点就是:它必须被任何继承了它们的 Derived class 重写,而且它们在 Base class 中通常没有定义。也就是说,在 Base class 中声明一个 pure virtual function,目的就是让 Derived class 只继承这个函数接口。

impure virtual function


同 pure virtual function 一样,Derived class 会继承其函数接口,同时可以 override,但不同的是,impure virtual function 函数会提供一份缺省的实现代码。也就是说,在 Base class 中声明一个 impure virtual function,目的就是让 Derived class 继承这个函数接口和一份缺省的实现。


但是,Derived class 获得这样一份缺省的实现,有时会带来一些问题。比如下面这种情形,航空公司目前只拥有 A 和 B 两种飞机机型,而且两者以相同的方式飞行:
class Airplane
{
public:
	virtual void fly(const Airport& destination);
	//...
};
void Airplane::fly(const Airport& destination)
{
	//缺省实现
}

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

为了表示所有的飞机能飞,并且不同的飞机原则上需要有不同的 fly 实现,所有 fly 函数被声明为 virtual 函数。由于目前拥有的 A 和 B 机型的飞行方式相同,为了避免代码重复,所以将 fly 声明为 impure virtual fucntion,在 Airplane::fly 中给了一份缺省的实现,同时被 ModelA 和 ModelB 继承。


那么现在,假设,航空公司又购买了一种新的 C 机型,它的飞行方式和 A 与 B 不同。此时,如果新添加的子类 ModelC 忘记了重写 fly 函数 (实际上也很有可能发生),这个时候 ModelC 就会获得 Airplane::fly 的一份缺省的实现,这份实现并不适用于 C 机型,这个时候就产生了错误。


其实这个问题的关键,并不在于 Airplane::fly 中提供了缺省的实现,而且子类 ModelC 并没说明自己要那一份缺省的实现,Airplane::fly 就给它了。所以这里,我们要解决的问题就是,如果子类明确提出了 “要一份缺省的实现”,那么 Base class 再给它,否则就不应该给。解决方法也不难,就是要 切断 virtual 函数的接口和缺省实现之间的连接,可以像下面这样:

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

像上面这样,将缺省的行为移到一个单独的函数之中,并将它设置为 protected 以及 non-virtual,供子类继承而又不对外开放。这样的话,如果子类需要这样一份缺省的实现,就可以直接在 fly 函数中对 defaultFly 作一个 inline 的调用:

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);
	}
}class ModelC : public Airplane
{
public:
	virtual void fly(const Airport& destination)
	{
		//defaultFly(destination); 不要这个缺省的实现
		//... 重新实现
	}
}

现在 ModelC 就不可能意外获得那样一份缺省的实现了,因为它自己必须重写 fly 函数。


此时,又有人会担心,fly 和 defaultFly 名称过于雷同而引起 class 命名空间污染问题。实际上,我们也可以利用:“pure virtual function 必须在 derived class 中重写,但是在 base class 中也可以拥有自己的实现” 这样一个事实:

class Airplane
{
public:
	virtual void fly(const Airport& des) = 0;
};

void Airplane::fly(const Airport& des)
{
	//缺省实现
}

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)
	{
		//... 重新实现
	}
}

non-virtual function


  • 如果成员函数是一个 non-virtual function,说明,它并不打算在 Derived class 中有不同的行为,无法被 Derived class override。也就是说,声明 non-virtual function 的目的就是为了让 Derived class 继承函数接口和一份强制性实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值