我们可以为 pure virtual 函数提供定义,但调用它的唯一途径是“调用时明确指出其 class 名称“。
让我们考虑 XYZ 航空公司设计的飞机继承体系。该公司只有A型和B型两种飞机,两者都以相同方式飞行。因此 XYZ
设计出这样的继承体系:
class Airport
{...
} ;
class Airplane
{
public:
virtual void fly(const Airport&
destina
ion);
} ;
void Airplane::fly(const Airport&
destination)
{
缺省代码,将飞机飞至指定的目的地
}
class ModelA: public
Airplane
{...
} ;
class ModelB: public
Airplane
{...
} ;
两个classes 共享一份相同性质(也就是它们实现 fly 的方式),所以共同性质被搬到
base
class
中,然后被这两个
classes
继承。这个设计突显出共同性质,避免代码重复,并提升未来的强化能力,减缓长期维护所需的成本。所有这些都是面向对象技术如此受到欢迎的原因。XYZ
航空公司应该感到骄傲。
现在,假设 XYZ
盈余大增,决定购买一种新式C
型飞机。A
型和B
型以及C型有某些不同。更明确地说,它的飞行方式不同。
XYZ 公司的程序员在继承体系中针对C
型飞机添加了一个
class,
但由于他们急着让新飞机上线服务,竟忘了重新定义其 fly
函数:
class
ModelC:
public
Airplane
{
}
;
然后代码中有一些诸如此类的动作:
Airpor POX(...);
Airplane* pa=
new
ModelC;
pa->fly(PDX);
//未声明 fly
函数
//POX 是我家附近的机场
//调用 Airplane:
:
fly
这将酿成大灾难;这个程序试图以 ModelA
ModelB
的飞行方式来飞
ModelC 这不是一个可以公开鼓舞旅游信心的行为。
问题不在 Airplane::fly
有缺省行为,而在千
ModelC
在未明白说出“我要“ 的情况下就继承了该缺省行为。幸运的是我们可以轻易做到“提供缺省实现给 derived classes,
但除非它们明白要求否则免谈”。此间技俩在于切断
“virtual
函数 接口”和其“缺省实现”之间的连接。下面是一种做法:
class Airplane
{
public:
virtual void fly(const Airport&
destination)= 0
;
protected:
void default Fly(cons Airport& destina
ion);
} ;
void
Airplane::defaultFly(const
Airport&
destina
ion)
{
缺省行为,将飞机飞至指定的目的地。
}
请注意, Airplane:
:
fly
已被改为一个
pure
virtual
函数,只提供飞行接口。其缺省行为也出现在 Airplane class
中,但此次系以独立函数
default Fly
的姿态出现,若想使用缺省实现(例如 ModelA
ModelB)
,可以在其
fly
函数中对
defaultFly 做一个 inline
调用(但请注意条款
30
所言,
inline
函数和
virtual
函数之间的交互关系):
class ModelA:
public
Airplane
{
public:
) ;
virtual void
fly(const
Airport&
destination)
{ defaul
tFly
(destination)
; }
class ModelB:
public
Airplane
{
public:
} ;
virtual void
fly(const
Airpor
七&
destination)
{ defaul
Fly(destination);)
现在 ModelC
class
不可能意外继承不正确的
fly
实现代码了,因为
Airplane 中的 pure
virtual
函数迫使
ModelC
必须提供自己的
fly
版本:
class ModelC:
public
Airplane
{
public:
virtual void fly(const
Airport&
destination);
);
void ModelC::fly(const
Airport&
des
ination)
{
C型飞机飞至指定的目的地
}
这个方案并非安全无虞,程序员还是可能因为剪贴 (copy-and-paste) 代码而招来麻烦,但它的确比原先的设计值得倚赖。至于 Airplane:
:
def
ault
Fly,
请注意 它现在成了 protected,
因为它是
Airplane
及其
derived
classes
的实现细目。乘客应该只在意飞机能不能飞,不在意它们怎么飞。 Airplane: :defaultFly
是个
non-virtual
函数,这一点也很重要。因为没有任何 一个 derived
class
应该重新定义此函数(见条款
36)
。如果
defaultFly
virtual 函数,就会出现一个循环问题:万一某些 derived
class
忘记重新定义
default Fly, 会怎样?
有些人反对以不同的函数分别提供接口和缺省实现,像上述的 fly defaultFly 那样。他们关心因过度雷同的函数名称而引起的
class
命名空间污染问题。但是他们也同意,接口和缺省实现应该分开。这个表面上看起来的矛盾该如何解决?唔,我们可以利用 “pure
virtual
函数必须在
derived
classes
中重新声明,但 它们也可以拥有自己的实现“这一事实。下面便是 Airplane
继承体系如何给
pure virtual 函数一份定义:
class Airplane
{
public:
virtual void fly(const Airport&
destination)= 0
;
};
void Airplane::fly(const
Airport
&
dest
ination)
//pure virtual
函数实现
{
缺省行为,将飞机飞全指定的目的地
}
class ModelA:
public
Airplane
{
public:
virtual void fly(cons Airport&
destination)
{ Airplane: :fly(destination); }
} ;
class ModelB: public
Airplane
{
public:
virtual void fly(cons Airport
&
destination)
{ Airplane: : fly (destinat
ion);
}
} ;
class ModelC:
public
Airplane
{
public:
virtual void fly(const Airport&
dest
inat
ion);
} ;
void ModelC::fly(const
Airport&
dest
ination)
{
C型飞机飞至指定的目的地
}
这几乎和前一个设计一模一样,只不过 pure
virtual
函数
Airplane:
:
fly
替换了独立函数 Airplane:
:defaultFly
。本质上,现在的
fly
被分割为两个基本要素: 其声明部分表现的是接口(那是 derived
classes
必须使用的),其定义部分则表现出缺省行为(那是 derived
classes
可能使用的,但只有在它们明确提出申请时才是)。如果合并 fly
defaultFly
,就丧失了“让两个函数享有不同保护级别”的机会: 习惯上被设为 protected
的函数
(defaultFly)
如今成了
public
(因为它在
fly
之中)。
——摘自<<Effective C++(第三版)>>