如果你了解C++各种特性的意义,你会发现,你对OOP的看法改变了。它不再是一项用来划分语言特性的仪典,而是可以让你通过它说出你对软件系统的想法。一旦你知道该通过它说些什么,移转至C++世界也就不再是可怕的高要求了。
条款32:确定你的public继承塑模出is-a关系
以C++进行面向对象编程,最重要的一个规则是:public inheritance(公有继承)意味is-a(是一种)的关系。
如果你令class D以public形式继承class B,你便是告诉C++编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。你的意思是B比D表现出更一般化得概念,而D比B表现出更特殊化的概念。你主张:“B对象可派上用场的任何地方,D对象一样可以派上用场”,因为每一个D对象都是一种(是一个)B对象。反之如果你需要一个D对象,B对象无法效劳,因为虽然每个D对象都是一个B对象,反之并不成立。
在C++领域中,任何函数如果期望获得一个类型为基类的实参(而不管是传指针或是引用),都也愿意接受一个派生类对象(而不管是传指针或是引用)。(只对public继承才成立。)
好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。
请记住:
C++的名称遮掩规则所做的唯一事情就是:遮掩名称。至于名称是否是相同或不同的类型,并不重要。即,只要名称相同就覆盖基类相应的成员,不管是类型,参数个数,都无关紧要。派生类的作用域嵌套在基类的作用域内。
C++的继承关系的遮掩名称也并不管成员函数是纯虚函数,非纯虚函数或非虚函数等。只和名称有关。
如果你真的需要用到基类的被名称遮掩的函数,可以使用using声明式,引入基类的成员函数。
请记住:
表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承。
请记住:
本条款告诉程序员,当需要使用virtual 函数时,可以考虑其他选择。
Virtual函数的替代方案是:
(1) 使用non-virtual interface(NVI)手法。思想是:将virutal函数放在private中,而在public中使用一个non-virtual函数调用该virtual函数。优点是:用一个不能被子类重定义的函数,做一些预处理、后处理等,子类只需要在private中重新实现virtual函数即可。即:基类给出virtual函数的使用方法,而派生类给出virtual函数的使用方法。
举例:
class GameCharacter {
public:
int healthValue() const{ // 1. 子类不能重定义
... // 2. preprocess
int retVal = doHealthValue(); // 2. 真正的工作放到虚函数中
... // 2. postprocess
return retVal;
}
...
private:
virtual int doHealthValue() const { // 3. 子类可重定义
...
}
};
(2) 将virtual函数替换为“函数指针成员变量”(这是Strategy设计模式中的一种表现形式)。优点是对象实例和派生类对象,可使用各种实现,也可在运行时随意改;缺点是:该函数不能访问类中的私有成员
举例:
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc); // default algorithm
class GameCharacter {
public:
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
{}
int healthValue() const {
return healthFunc(*this);
}
...
private:
HealthCalcFunc healthFunc;
};
(3) 以tr1::function成员变量替换virtual函数,这允许使用任何可调用物搭配一个兼容于需求的签名式。这也是Strategy设计模式的某种形式。这种方式比上面的函数指针更灵活、限制更少:[1]返回值不一定是int,与其兼容即可; [2]可以是function对象; [3]可以是类的成员函数。
(4) 继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现手法。这种方式最大的优点是:可以随时添加新的算法。举例:
class GameCharacter;
class HealthCalcFunc {
public:
...
virtual int calc(const GameCharacter& gc) const
{ ... }
...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter {
public:
explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
: pHealthCalc(phcf)
{}
int healthValue() const {
return pHealthCalc->calc(*this);
}
...
private:
HealthCalcFunc *pHealthCalc;
};
请记住:
本条款告诫程序员:绝不要重新定义继承而来的non-virtual函数,因为这不仅容易造成错误,而且是一种自相矛盾的设计。
条款37:绝不重新定义继承而来的缺省参数值
该条款告诫程序员:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数-你唯一应该覆写的东西-却是动态绑定。
条款38:通过符合塑模出has-a或“根据某物实现出”
(1)如果class之间的继承关系是private。编译器不会自动将一个derived class对象转化为一个base class对象。由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原来是protected或public属性。
(2)private继承意味is-implemented-in-terms-of,它的级别比组合低,当derived class需要protected base class或者需重新定义继承而来的virtual class时,设计才是合理的。
(3)与复合不同 ,private继承可以使empty base空间最优化。举例:
class Empty{}; //empy class
clsss HoldsAnyInt{
private:
int x;
Empty e;
};//这个的大小为>sizeof(int),Empty空对象需要安插一个char到空对象,并且有齐位需求。
class HoldsAnyInt::private Empty{
private:
int x;
}; //这个sizeof大小为sizeof(int)
补充:
class HoldsAnyInt::private Empty{
private:
int cal() = 0;
int x;
}; //这个sizeof大小为8, 实际上为size(int) + sizeof(vptr)
条款40:明智而审慎地使用多重继承
(1) 多重继承比单一继承复杂。他可能导致新的歧义性,以及virtual继承的需要
(2) Virtual继承会增加大小、速度、初始化复杂度等等成本。如果virtual base classed不带任何数据,将是最具使用价值的情况。
(3) 多重继承最正当用途是:其中一个设计“public 继承某个interface class”和“priavte继承某个协助实现的class”的两相结合。