王朝网络
分享
 
 
 

翻译:Effective C++, 3rd Edition, Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)

王朝c/c++·作者佚名  2006-05-27
宽屏版  字体: |||超大  

(点击此处,接上篇)

现在,假设 XYZ 公司的财富增长了,决定引进一种新机型,Model C。Model C 在某些方面与 Model A 和 Model B 不同。特别是,它的飞行不同。

XYZ 公司的程序员在 hierarchy(继承体系)中增加了 Model C 的 class,但是由于他们匆匆忙忙地让新的机型投入服务,他们忘记了重定义 fly function:

class ModelC: public Airplane {

... // no fly function is declared

};

于是,在他们的代码中,就出现了类似这样的东西:

Airport PDX(...); // PDX is the airport near my home

Airplane *pa = new ModelC;

...

pa->fly(PDX); // calls Airplane::fly!

这是一个灾难:企图让一个 ModelC object 像一个 ModelA 或 ModelB 一样飞行。这在旅行人群中可不是一种鼓舞人心的行为。

这里的问题并不在于 Airplane::fly 有缺省的行为,而是在于 ModelC 被允许不必明确说出它要做什么就可以继承这一行为。幸运的是,“为 derived classes(派生类)提供缺省的行为,但是除非它们提出明确的要求,否则就不交给他们”是很容易做到的。这个诀窍就是切断 virtual function(虚拟函数)的 interface(接口)和它的 default implementation(缺省实现)之间的联系。以下用的就是这个方法:

class Airplane {

public:

virtual void fly(const Airport& destination) = 0;

...

protected:

void defaultFly(const Airport& destination);

};

void Airplane::defaultFly(const Airport& destination)

{

default code for flying an airplane to the given destination

}

注意 Airplane::fly 是被如何变成一个 pure virtual function(纯虚拟函数)的。它为飞行提供了 interface(接口)。那个缺省的实现也会出现在 Airplane class 中,但是现在它是一个独立的函数,defaultFly。像 ModelA 和 ModelB 这样需要使用缺省行为的 Classes 只是需要在他们的 fly 的函数体中做一下对 defaultFly 的 inline 调用(但是请参见 Item 30 提供的关于 inline 化和 virtual functions(虚拟函数)的交互作用的信息):

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 class,不可能在无意中继承到不正确的 fly 的实现,因为 Airplane 中的 pure virtual(纯虚拟)强制要求 ModelC 提供的它自己的 fly 版本。

class ModelC: public Airplane {

public:

virtual void fly(const Airport& destination);

...

};

void ModelC::fly(const Airport& destination)

{

code for flying a ModelC airplane to the given destination

}

这一方案并非十分安全(程序员还是能通过 copy-and-paste 使他们自己陷入麻烦),但是它比最初的设计更加可靠。至于 Airplane::defaultFly,它是 protected(保护的)是因为它完全是 Airplane 和它的 derived classes(派生类)的实现细节。使用飞机的客户应该只在意它能飞,而不必管飞行是如何实现的。

Airplane::defaultFly 是一个 non-virtual function(非虚拟函数)这一点也很重要。这是因为 derived class(派生类)不应该重定义这个函数,这是一个在 Item 36 中专门介绍的原则。如果 defaultFly 是 virtual(虚拟的),你就会遇到一个循环的问题:如果某些 derived class(派生类)应该重定义 defaultFly 却忘记了的时候会如何呢?

一些人反对为 interface(接口)和 default implementation(缺省实现)分别提供函数,就像上面的 fly 和 defaultFly 那样。首先,他们注意到,这样做会导致类似的相关函数名污染 class namespace(类名字空间)的问题。然而他们仍然同意 interface(接口)和 default implementation(缺省实现)应该被分开。他们是怎样解决这个表面上的矛盾呢?通过利用以下事实:pure virtual functions(纯虚拟函数)必须在 concrete derived classes(具体派生类)中被 redeclared(重声明),但是它们也可以有它们自己的实现。以下就是 Airplane hierarchy(继承体系)如何利用这一能力定义一个 pure virtual function(纯虚拟函数):

class Airplane {

public:

virtual void fly(const Airport& destination) = 0;

...

};

void Airplane::fly(const Airport& destination) // an implementation of

{ // a pure virtual function

default code for flying an airplane to

the given 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);

...

};

void ModelC::fly(const Airport& destination)

{

code for flying a ModelC airplane to the given destination

}

除了用 pure virtual function(纯虚拟函数)Airplane::fly 的函数体代替了独立函数 Airplane::defaultFly 之外,这是一个和前面的几乎完全相同的设计。本质上,fly 可以被拆成两个基本组件。它的 declaration(声明)指定了它的 interface(接口)(这是 derived classes(派生类)必须使用的),而它的 definition(定义)指定它的缺省行为(这是 derived classes(派生类)可以使用的,但只是在他们明确要求这一点时)。将 fly 和 defaultFly 合并,无论如何,你失去了给予这两个函数不同的保护层次的能力:原来是 protected 的代码(通过位于 defaultFly 中实现)现在成为 public(因为它位于 fly 中)。

最后,我们看看 Shape 的 non-virtual function(非虚拟函数),objectID:

class Shape {

public:

int objectID() const;

...

};

当一个 member function(成员函数)是 non-virtual(非虚拟的)时,不应该指望它在 derived classes(派生类)中的行为会有所不同。实际上,一个 non-virtual member function(非虚拟成员函数)指定了一个 invariant over specialization(超越特殊化的不变量),因为不论一个 derived class(派生类)变得多么特殊,它都把它看作是不允许变化的行为。如下所指除的,

声明一个 non-virtual function(非虚拟函数)的目的是 to have derived classes inherit a function interface as well as a mandatory implementation(使派生类既继承一个函数的接口,又继承一个强制的实现)。你可以这样考虑 Shape::objectID 的声明,“每一个 Shape object 有一个产生 object identifier(对象标识码),而且这个 object identifier(对象标识码)总是用同样的方法计算出来的,这个方法是由 Shape::objectID 的定义决定的,而且 derived class(派生类)不应该试图改变它的做法。”因为一个 non-virtual function(非虚拟函数)被看作一个 invariant over specialization(超越特殊化的不变量),在 derived class(派生类)中他绝不应该被重定义,细节的讨论参见 Item 36。

对 pure virtual,simple virtual,和 non-virtual functions 的声明的不同允许你精确指定你需要 derived classes(派生类)继承什么东西。分别是 interface only(仅有接口),interface and a default implementation(接口和一个缺省的实现),和 interface and a mandatory implementation(接口和一个强制的实现)。因为这些不同的声明类型意味着根本不同的意义,当你声明你的 member functions(成员函数)时你必须在它们之间仔细地选择。如果你这样做了,你应该可以避免由缺乏经验的类设计者造成的两个最常见的错误。

第一个错误是声明所有的函数为 non-virtual(非虚拟)。这没有给 derived classes(派生类)的特殊化留出空间;non-virtual destructors(非虚拟析构函数)尤其有问题(参见 Item 7)。当然,完全有理由设计一个不作为 base class(基类)使用的类。在这种情况下,一套独享的 non-virtual member functions(非虚拟成员函数)是完全合理的。然而,更通常的情况下,这样的类既可能出于对 virtual(虚拟)和 non-virtual functions(非虚拟函数)之间区别的无知,也可能是对 virtual functions(虚拟函数)的性能成本毫无根据的担心的结果。事实是,几乎任何作为 base class(基类)使用的类都会有 virtual functions(虚拟函数)(还是参见 Item 7)。

如果你关心 virtual functions(虚拟函数)的成本,请允许我提起基于经验的 80-20 规则(参见 Item 30),在一个典型的程序中的情况是,80% 的运行时间花费在执行其中的 20% 的代码上。这个规则是很重要的,因为它意味着,平均下来,你的函数调用中的 80% 可以被虚拟化而不会对你的程序的整体性能产生哪怕是最轻微的可察觉的影响。在你走进对“你是否能负担得起一个 virtual function(虚拟函数)的成本”忧虑的阴影之前,应该使用一些简单的预防措施,以确保你关注的是你的程序中能产生决定性不同的那 20%。

另一个常见的错误声明所有的 member functions(成员函数)为 virtual(虚拟)。有时候这样做是正确的—— Item 31 的 Interface classes(接口类)可以作为证据。然而,它也可能是缺乏表明态度的决心的类设计者的标志。某些函数在 derived classes(派生类)中不应该被重定义,而且只要在这种情况下,你都应该通过将那些函数声明为 non-virtual(非虚拟)而明确地表达这一点。它不是为那些人服务的,他们假设如果他们只需花一些时间重定义你的所有函数,你的类就会被所有的人用来做所有的事情,如果你有一个 invariant over specialization(超越特殊化的不变量),请直说,不必害怕!

Things to Remember

Inheritance of interface(接口继承)与 inheritance of implementation(实现继承)不同。在 public inheritance(公开继承)下,derived classes(派生类)总是继承 base class interfaces(基类接口)。Pure virtual functions(纯虚拟函数)指定 inheritance of interface only(仅有接口被继承)。Simple (impure) virtual functions(简单虚拟函数)指定 inheritance of interface(接口继承)加上 inheritance of a default implementation(缺省实现继承)。Non-virtual functions(非虚拟函数)指定 inheritance of interface(接口继承)加上 inheritance of a mandatory implementation(强制实现继承)。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有