六:继承与面向对象设计
条款32:确定你的public继承塑模出is-a关系
如果你令class B(派生类)以public形式继承class A(父类),你便是告诉C++编译器说,每一个类型为D的对象同时也是一个类型为B的对象。
请记住:“public继承”意味is-a。适用于base classes身上的每一件事情一定也适用于derived class身上,因为每一个derived class 对象也都是一个base class 对象。
条款33:避免遮掩继承而来的名称
class Base{
private:
int x;
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2();
void mf3();
void mf3(double);
};
class Derived: public Base{
public:
virtual void mf1();
void mf3();
void mf4();
};
int main(){
Derived d;
int x;
...
d.mf1(); //没问题,调用Derived::mf1
d.mf1(x); //错误!因为Derived::mf1遮掩了Base::mf1
d.mf2();//没问题,调用Base::mf2
d.mf3(); //没问题,调用Derived::mf3
d.mf3(x); //错误!因为Derived::mf3遮掩了Base::mf3
return 0;
}
Derived class 可以更改为:
class Derived: public Base{
public:
using Base::mf1;//让Base class内名为mf1和mf3的所有东西在Derived作用域内都可见。
using Base::mf3;
virtual void mf1();
void mf3();
void mf4();
};
如果Derived以private继承Base,而Derived唯一想继承的mf1是那个无参数版本。using声明在这里派不上用场,因为using声明式会令继承而来的某给定名称的所有同名函数在derived class中都可见。此时需要不同的技术,即一个简单的转交函数
class Derived: private Base{
public:
virtual void mf1() //转交函数
{ Base::mf1();}
};
Derived d;
int x;
d.mf1();//很好,调用的是Derived::mf1
d.mf1(x); //错误!Base::mf1()被遮掩了
请记住:1.derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。(is-a)
2.为了让被遮掩的名称再见天日,可使用using声明式或转交函数。
条款34:区分接口继承和实现继承
声明一个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 函数以外的其他选择
藉由Non-Virtual Interface手法实现Template Method模式。
NVI手法的一个优点隐身在下述代码注释“做一些事前工作”,“做一些事后工作”之中。这意味外覆器确保得以在一个virtual函数被调用之前设定好适当场景,并在调用结束之后清理场景。。如果你让客户直接调用virtual 函数,就没有任何好办法可以做这些事。
class A{
public:
int healthValue() const{
... //做一些事前工作
int retVal = doHealthValue();
... //做一些事后工作
return retVal;
}
private:
virtual int doHealthValue() const {
...
}
};
请记住:1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
2.将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
3.tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。
条款36:绝不重新定义继承而来的non-virtual函数
class B{
public:
void mf();
};
class D:public B {
void mf();//遮掩了B::mf
};
D x;
//如以下行为
B *pB = &x;
pB->mf();
//异于以下行为
D *pD = &x;
pD->mf();
/*
造成此行为的原因是,non-virtual 函数如B::mf 和D::mf都是静态绑定
*/
请记住:绝对不要重新定义继承而来的non-virtual函数。
条款37:绝不重新定义继承而来的缺省参数值
class Shape{
public:
enum ShapeColor
{
Red,
Green,
Blud
};
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle : public Shape {
public:
//注意,赋予不用的缺省参数值。这真糟糕!
virtual void draw(ShapeColor color = Green) const;
};
class Circle : public Shape {
public:
/*
请注意:以上这么写则当客户以对象调用此函数,一定要指定参数值。因为静态绑定下这个函数并不从其base继承缺省参数值。
但若以指针(或引用)调用此函数,可以不指定参数值,因为动态绑定下这个函数会从其base继承缺省参数值。
*/
virtual void draw(ShapeColor color) const;
};
Shape *pr = new Rectangle; //静态类型为Shape *
pr->draw(); //调用Rectangle::draw(Shape::Red)! Rectangle::draw的缺省参数值应为GREEN,但由于pr的静态类型是Shape *
//,所以此一调用的缺省参数值来自Shape class而非Rectangle。
正确版本:
class Shape{
public:
enum ShapeColor
{
Red,
Green,
Blud
};
void draw(ShapeColor color = Red) const{
doDraw(color);
}
private:
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle : public Shape {
public:
...
private:
virtual void draw(ShapeColor color) const;
};
//由于non-virtual函数应该绝对不被derived classes覆写,这个设计很清楚的使得draw函数的color缺省参数值总是为Red。
请记住:绝对不要重新定义一个继承而来的缺省值,因为缺省参数值都是静态绑定(用的是静态类型的缺省值,不是你自己定义的),而virtual函数----你唯一应该覆写的东西----却是动态绑定。
条款38:通过复合塑模出has-a或“根据某物实现出”
复合是类型之间的一种关系,当某种类型的对象内含它种类型的对象,便是这种关系。
在应用域复合意味has-a(有一个),在实现域,意味is-implemented-in-terms-of(根据某物实现出)。书p186
请记住:1.复合的意义和public继承完全不同。
2.在应用域复合意味has-a(有一个),在实现域,意味is-implemented-in-terms-of(根据某物实现出)。
条款39:明智而审视地使用private继承
一个空类 class Empty{}; 其大小不为0,如不考虑齐位要求,在大多是编译器中sizeof(Empty)=1(安插一个char到空对象中)。如果不独立,如class B:public Empty{private: int x;},则sizeof(B)=sizeof(int) (空白基类最优化)。
现实中的“empty class”并不真的是empty。虽然他们从未拥有non-static成员变量,却往往内含typedefs,enums,static成员变量,或non-virtual函数。
请记住:1.private继承意味is-implemented-in-terms-of(根据某物实现出)。他通常比复合的级别低。但是当derived class需要访问protected base class 的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2.和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
条款40:明智而审视地使用多重继承
请记住:1.多重继承比单一继承更复杂。他可能导致新的歧义性,以及对virtual继承的需要。
2.virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base classes不带任何数据,将是最具实用价值的情况。
3.多重继承的确有正当用途。其中一个情节涉及“public继承某个interface class”和“private继承某个协助实现的class”的两相组合。