Effective C++ — 继承与面向对象设计
2017-09-25 07:44
288 查看
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::mf pD->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"的两相结合
相关文章推荐
- Effective C++ 3nd 读书摘要(六、继承与面向对象设计)Item32 - 40
- 【Effective C++】继承与面向对象设计
- Effective C++第六章-继承与面向对象设计
- Effective C++学习有感--第六章 继承与面向对象设计(二)
- Effective C++ 读书笔记(六) 继承与面向对象设计
- Effective C++ 精要(第六部分:继承与面向对象设计)
- Effective C++ 第六章--继承与面向对象设计笔记
- Effective C++ 读书笔记(35-44):继承关系与面向对象设计
- Effective C++ —— 继承与面向对象设计(六)
- Effective C++ -- 继承与面向对象设计
- Effective C++ ——继承与面向对象设计
- Effective C++ --6 继承与面向对象设计
- Effective C++ ——继承与面向对象设计
- 《Effective C++》继承与面向对象设计
- Effective C++第6章 继承与面向对象设计(条款36-40)
- Effective C++ ——继承与面向对象设计
- Effective C++之继承和面向对象设计
- effective C++: 6.继承与面向对象设计
- Effective C++第6章继承与面向对象设计(条款32-34)
- effective c++之继承与面向对象设计