Effective C++ — 继承与面向对象设计

来源:互联网 发布:数控折弯编程教学视频 编辑:程序博客网 时间:2024/06/08 12:27

Effective C++

_________________________________________________________________




面向对象编程几乎已经风靡两个时代了,所以关于继承,派生,virtual函数等等.但是C++的oop有可能和你原本习惯的oop稍有不同:继承可以单一继承

或者多继承,每一个继承连接可以是public,protected或private,也可以是virtual或者non-virtual.然后是成员函数的各个选项:virtual? non-

virtual?pure virtual?以及成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设

计选型有哪些?如果class的行为需要修改,virtual函数是最佳选择吗?接下来就见证奇迹的时刻了.



条款32:确定你的Public继承塑模出is-a关系



public继承和is-a之间的等价关系听起来颇为简单,但有时候你的直觉可能会误导你.举个例子,大家都知道企鹅是一种鸟,这是事.鸟也可以飞这也

是事实.如果我们天真的以C++描述这层关系,结果如下:

class Bird{public:virtual void fly();...};class penguin :public Bird{...};

这个时候我们遇上了乱流,因为这个继承体系说企鹅可以飞,而我们知道那不是真的,怎么回事?现在开始谨慎一点,我们应该承认一个事实:

有数种鸟不会飞.我们来到下面的继承关系,他塑模出较佳的真实性:

class Bird{...   //没有声明fly函数};class FlyingBird :public Bird{public:virtual void fly();...};class penguin :public Bird{...//没有声明fly函数};

即便如此,此刻我们仍然未能完全处理好这些鸟事,世界并不存在一个"适用于所有软件"的完美设计.所谓最佳设计,取决于系统希望做什么事情.这里

还有一种思想. 别处理我所谓"所有鸟都会飞,企鹅是鸟,但企鹅不会飞"就是为企鹅重新定义fly函数,令他产生一个运行期错误.


void error(const string& msg); //定义于某处class Penguin :public Bird{public:virtual void fly(){error("Attempt to make a penguin fly!");}.....};

很重要的是,你必须认知这里所说的某些东西可能和你所想的不同.这里并不是说"企鹅不会飞",而是说"企鹅会飞,但尝试那么做是错误的.

总结:

public继承意味着"is-a"关系.适用于base class身上的每一件事情一定也适用于derived classes身上

,因为每个derived classes也都是一个base classes对象.


条款33:避免遮掩继承而来的名称



总结:

derived classes内的名称会遮掩base classes内的名称. 在public继承下从来没有人希望

如此为了让被遮掩的名称重见天日,可使用using生命式或转交函数.



条款34:区分接口继承和实现继承



表面上直接了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承.这两种继承的差异,

很像函数声明和函授定义之间的差异.身为class的设计者,有时候你会希望derived classes只继承成员函数的接口;有时候你又会希

望derived classes同时继承函数的接口和实现,但又希望能够覆写他们所继承的实现;又有时候你希望derived classes同时继承函数

的接口和实现,并且不允许覆写任何东西.pure virtual函数有两个最突出的特性:他们必须被任何"继承了他们"的具备class重新声明

,而且他们在抽象class中通常没有定义.把这两个性质摆在一起,你就会明白.


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

声明简朴的impure virtual函数的目的,是让derived classes继承该函数的接口和却省实现.

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


总结:

1.接口继承和实现继承不同.在public继承下,derived classes总是继承base class的接口.

2.pure virtual函数只具体制定接口继承

3.简朴的impure virtual函数具体指定接口继承及却省实现继承.

4.non-virtual函数具体指定接口继承以及强制性实现继承.


 
条款35:考虑virtual函数之外的其他选择



总结:

1.virtual函数的替代方案包括 NVI方法 及 Strategy设计模式的多种形式。NVI方法 自身是一个特殊形式的 Template Method 设

计模式。


2.将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class内的 non-public成员。


3.tr1::function 对象的行为就像一般的函数指针。这样的对象可接纳“与给定的目标签名式兼容”的所有可调用物。



条款36:绝不重新定义继承而来的non-virtual函数

假设我告诉你,class D系由class B以public形式派生而来,class B定义有一个public成员函数mf,由于mf的参数和返回值都不重


要,所以假设两者皆为void.换句话讲我的意思是:


class B{public:void mf();...};class D:public B{....};


虽然我们对B,D和mf一无所知,但面对一个类型为D的对象x:

D x;   //x是一个类型为D的对象


如果以下行为:


B* pB = &x; //获得一个指针指向x

pB->mf();   //经由该指针调用mf


异于以下行为:


D* pD = &x;   //获得一个指针指向x

pD->mf();     // 经由该指针调用mf


你可能会相当惊讶.毕竟两者都通过对象x调用成员函数mf.由于两者所调用的函数都相同,凭借的对象也相同,所以行为也应该相同,是吗?


是的,理应如此,但事实可能不是如此.更明确的说,如果mf是个non-virtual函数而D定义有一个自己的mf版本,那就不是如此:

class D :public B{public:void mf(); //hides 隐藏了B::mf....};pB->mf();   //调用B::mfpD->mf();   //调用D::mf

造成此一两面行为的原因是,non-virtual函数如B::mf和D::mf都是静态绑定. 这个意思就是,由于pB被声明为一个Pointer-to-

B,通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为"b派生之class的对象",一如本例


但另一方面,virtual函数确实动态绑定,所以他们不受这个问题苦恼.如果mf为一个virtual函数,不论是通过pB还是pD调用mf都会导致调用D::mf,

因为pB和pD真正指的都是一个类型为D的对象.


如果你正在编写class D并重新定义继承自class D的non-virtual函数mf,D对象很可能展现出精神分裂的不一致行径.更明确的说,当mf调用,任何一

个D对象都可能展现出B或D的行为;决定因素不在对象本身,而在于"指向该对象之指针"当初的声明类型.Reference也会展现和指针一样难以理解的行

径.


条款32教会我们如果是public继承,那么就要符合is-a关系. 那么:


1.适用于B对象的每一件事情,也同样适用于D对象,因为D对象都是一个B对象;


2.B的derived classes一定会继承md的接口和实现,因为mf是B的一个non-virtual函数.


现在,如果D重新定义mf,你的设计就会出现矛盾,不论从任何情况下,都不应该重新定义一个继承而来的non-virtual函数.


最后总结: 绝对不要重新定义继承而来的non-virtual函数.


条款37:绝不重新定义继承而来的缺省参数值


让我们一开始就将讨论简化.你只能继承两种函数:virtual和non-virtual函数.然而重新定义一个继承而来的non-virtual函数永远


都是错的,所以我们可以安全地将本条款的讨论局限于"继承一个带有缺省参数值的virtual函数"


这种情况下,本条款成立的理由就非常直接而明确了:virtual函数系动态绑定,而缺省参数值却是静态绑定.


何为动态绑定和静态绑定?????  浅析静态绑定和动态绑定 这里面有你想要的答案.



总结:


绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,


而virtual函数-你唯一应该覆写的东西-却是动态绑定.




条款38:通过复合塑模出has-a或者"根据某物实现出"


1.复合的意义和public继承完全不同


2.在应用域,复合以为has-a.在实现域,复合意味is-implemented-in-terms-of



条款39:明智而审慎地使用private继承


1.private继承意味is-implemented-in-trems of(根据某物实现出).它通常比复合的级别低.但是当derived class需要访问protected base

 class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的.


2.和复合不同,private继承可以造成empty base最优化. 这对致力于对象尺寸最小化""的程序库开发者而言,可能很重要.


条款40:明智而审慎地使用多重继承


一旦涉及多重继承,C++社群便分为两个基本阵营.其中之一认为如果单一继承是好的,多重继承一定是更好.另一阵营则主张,单一继承是好的

多重继承不值得拥有.他们两个阵营都是会有自己支撑的理由,下面我们来瞧瞧:


菱形继承问题:菱形继承的对象模型探究以及解决菱形继承数据冗余和二义性问题


总结:


1.多重继承比单一继承复杂,他可能导致新的歧义性,以及对virtual继承的需要.

2.virtual继承会增加大小,速度,初始化复杂度等待成本.如果virtual base class不带任何数据,将是最具使用价值的情况.

3.多重继承的确有正当用途。其中一个情节涉及"Public继承某个Interface class"和"private继承某个协助实现的class"的两相结合



阅读全文
1 0