继承与面向对象设计
条款32:确定你的public继承塑模出is-a关系
public继承意味is-a。适用于base classes身上的每一件事情一定适用于derived classes身上,以为每一个derived class对象也一定是一个base class对象
条款33:避免遮掩继承而来的名称
derived class作用域被嵌套进base class作用域内,类似于local和global。derived中的函数会遮掩base中的所有同名函数,即使他们参数不同。
解决方案 用using声明或者用转交函数
class Base{
public:
virtual void mf1();
virtual void mf1(int);
...
};
class Derived:private Base{
public:
using Base::mf1;//using 申明
virtual void mf1(int x)//转交函数
{
Base::mf1(x);
}
...
};
条款34:区分接口继承和实现继承
身为class设计者,有时你会希望derived classes只继承成员函数的接口;有时你又会希望derived class同时继承函数的接口和实现,但又能override它们所继承的实现;有时你希望derived classes同时继承函数的接口和实现,并且不允许覆写任何东西。
接口继承和实现继承不同(就像声明和定义)。
pure virtual函数指定接口继承
简朴的impure virtual函数指定接口继承和缺省实现继承
non-virtual函数具体指定接口继承以及强制性实现继承
pure virtual函数:只提供接口
impure virtual函数:提供接口和缺省实现。这里,接口和缺省实现应该分开。这里有2种方法
– 将这种函数变成pure virtual的形式,同时提供另一个函数作为缺省实现,在派生类中,暴露这个缺省实现(protected)
class Airplane{
public:
virtual void fly(const Airport& destination)=0;
...
protected:
void defaultFly(const Airport& destination);//缺省实现,供派生类调用
}
– 将它声明为pure virtual并提供实现,在派生类的对应函数中调用基类的缺省实现(调用纯虚函数的唯一途径是“调用时明确指出其class类型”)
non-virtual是为了令derived classes继承函数的接口和一份强制实现。它代表的意义是不变性凌驾于特异性
两大错误:所有函数声明为non-virtual(使得derived classes没有空余空间进行特化);所有函数声明为virtual(立场不坚定,有些函数就是不该被重定义)
virtual函数的成本:80-20法则(程序80%的执行时间花在20%的代码上),当你担心virtual的成本,请先将精力放在20%的代码上
条款35:考虑virtual函数以外的其他选择
假设我们现在写一款游戏,不同角色攻击时会释放不同的技能,可能我们会想到使用virtual函数,让特殊角色都继承于GameCharacter,GameCharacter提供了一份缺省实现,特殊角色可针对自己的情况改写攻击函数。
当然这也是我们最常规的办法,除此之外,也存在着许多其他的方式供我们选择。
Non-Virtual Interface的手法实现Template Method模式
lass GameCharacter{
public:
void attack(){
beforeAttack();
doAttack();
afterAttack();
}
private:
virtual void doAttack(){
...
}
};
这就是所谓的NVI手法,attack是作为doAttack的外覆器。
NVI的优势在于我们可以在进行实际操作前后做些处理,正如我们beforeAttack(),afterAttack()写的那样。
基于Function Pointers实现的Strategy模式
我们可以让不同角色保存一个函数指针,该函数指针执行特殊攻击操作。但是这样存在一个问题,就是函数指针指向的函数可能需要访问对象的私有元素,这样可能就需要采用friend关键字来为函数特殊访问权限。
基于tr1::function实现的Strategy模式
与上面的Function Pointers相似,只不过std::function具有更好的封装,可以保存成员函数。
条款36:绝不重新定义继承而来的non-virtual函数
父类的non-virtual函数体现了某种不变性,一旦子类改变定义,便是对is-a关系的违反。如果希望子类对某些函数表现出特异性,这时就需要virtual关键字,virtual函数通过虚函数表的机制,向子类提供了一种保证:你可以重新定义我,我将仍然维护is-a关系。
条款37:绝不重新定义继承而来的缺省参数值
virtual函数是动态绑定的,而缺省参数值是静态绑定
这意味着你在调用一个定义于derived class内中的virtual函数的同时,使用的是base class为它指定的缺省参数指
条款38:通过复合塑模出has-a或“根据某物实现出”
复合是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。
程序中的对象其实相当于你所塑造的世界中的某些事物,例如人、汽车、视频画面等等。这样的对象属于应用域部分。其他对象则纯粹是实现细节上的人工制品,像是缓冲区、互斥器、查找树等等。这些对象属于实现域部分。复合发生于应用域内对象之间,表现出has-a关系,当发生于实现域内则是表现is-implemented-in-terms-of关系。
条款39:明智而审慎地使用private继承
如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。
class Person{
...
};
class Student:private Person{
...
};
void eat(const Person&);
void study(const Student&);
Person p;
Student s;
eat(p);//正确
eat(s);//错误,无法转化
private继承意味着implemented-in-terms-of。如果让D以private形式继承B,用意是采用B中已经备妥的某些特性,不是因为B和D有任何观念上的关系。
和复合不同,private继承可以造成empty base最优化。这有利于空间。一般情况下依然用复合实现implemented-in-terms-of
条款40:明智而审慎地使用多重继承
多重继承比单一继承复杂。可能导致歧义性,以及对virtual继承的需要。
virtual继承会增加大小、速度、初始化复杂度等等成本。如果virtual base classes不带任何数据,将是最有使用价值的情况。
多重继承有正当用途。其中一个情节涉及”public继承某个Interface class”和“private继承某个协助实现的class“两相组合