effective C++读书笔记(六)

来源:互联网 发布:程序员的怒喊 编辑:程序博客网 时间:2024/06/09 01:18

6. 继承和面向对象设计(Inheritanceand Object-Oriented Design)

条款32:确定你的public 继承塑模出is-a关系(Makesure public inheritance models “is-a”)

“Public 继承意味着”is-a”关系。有就是说:所有适用于baseclasses身上的每一件事情也一定适用于derivedclasses,因为每个derivedclass对象也是一个baseclass对象。注意书中给出的企鹅是鸟不能飞的例子,正方形继承长方形。

好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。

 

条款33:避免遮掩继承而来的名称(Avoid hiding inherited names)

           继承方式

基类属性

 

public

 

protected

 

private

公有继承(public)

public

protected

不可见

保护继承(protected)

protected

protected

不可见

私有继承(private)

private

private

不可见

1derivedclasses内的名称会遮掩baseclasses内的名称(即使变量的类型不同,或者函数的参数不同),局部变量名会遮掩全局作用域的变量名。即,只要名称相同就覆盖基类相应的成员,不管是类型,参数个数,都无关紧要。派生类的作用域嵌套在基类的作用域内C++的继承关系的遮掩名称也并不管成员函数是纯虚函数,非纯虚函数或非虚函数等。只和名称有关。如下面代码所示:

class Base{
 int x;
 virtual void mf1(int);
};

class Derived:publicBase{
 double x;
 virtual void mf1();
};

void main()
{
 Derived d;
 d.x =10;

 d.mf1(10);//错误!因为Derived::mf1遮掩了Base::mf1
}

2)如果想继续访问基类内的变量(或函数),可以使用using声明式(usingBase:: mf1)或转交函数(如下所示)。

class  Derived:privateBase{
public:
 virtual void mf1() //转交函数(forwardingfunction
 {
  Base::mf1();
 }
};

看下面一个例子:

classBase{

private:

    int x ;

public:

    virtualvoid mf1 = 0 ;

    virtualvoid mf2() ;

    voidmf3() ;

   ....

}

 

classDerived : public Base{

public:

   virtual void mf1() ;

    voidmf4();

   ...

}

 

voidDerived::mf4()

{

    ....

    mf2();

   ....

}

我们看mf4mf2的查找顺序:先在mf4的作用域中找,没找到mf2,往外走,在Derived作用域找,没找到,往外,在baseclass中找到了,OK,结束,如果还是没找到的话,就再namespace作用域中找,最后到global作用域找

 

上面一切OK,但是一旦基类有重载成员,而继承类只改写了一个,那么,基类的其他重载函数就不可见了,看下面的例子:

classBase{

private:

    int x ;

public:

    virtualvoid mf1 = 0 ;

    virtualvoid mf1(int) ;

    virtualvoid mf2() ;

    voidmf3() ;

    voidmf3(double) ;

   ....

}

 

classDerived : public Base{

public:

   virtual void mf1() ;

    voidmf3() ;

    voidmf4();

   ...

}

 

Derivedd ;

intx ;

d.mf1(); 

d.mf1(x); // error

d.mf2();

d.mf3();

d.mf3(double); // error

好,如何解决上面的问题,两种方案,一是用using,而是用转接函数

方案1

classDerived : public Base{

public:

   using Base::mf1 ;

    usingBase::mf3 ;

   virtual void mf1() ;

    voidmf3() ;

    voidmf4();

   ...

}

这样一改上面就不会错了

 

方案2:可能using编译器不支持,也可能你不想全部要,只要一部分

classDerived : private Base{

public:

   virtual void mf1()  //转交函数

   {

       Base::mf1() ;

    }

}

 

总结:

1derivedclasses内的名称会遮掩baseclasses内的名称,在public继承下,从来没有人希望如此

2、为了让被遮掩的base名称能够重见天日,可以用两种方案:使用using或者使用转交函数

 

条款34: 区分接口继承和实现继承(Differentiatebetween inheritance of interface and inheritance of implementation)

本条款主要讲函数接口继承(也就是声明)和函数实现继承,以及purevirtual函数、simple(impure)virtualnon-virtual函数之间的差异。

1接口继承和实现继承不同。在public继承之下,derived classes总是继承baseclass的接口(这个没理解)。

2 purevirtual函数只具体指定接口继承(要求继承者实现该接口);impure virtual函数具体指定接口继承及缺省实现继承(继承者可自己实现该接口也可使用缺省实现);non-virtual函数具体指定接口继承以及强制性实现继承(继承者必须使用该接口在父类的实现)。三类函数的形式如下:

class Shape{
public:
 virtual void draw() const = 0;//purevirtual 函数
 virtual void error(const std::string& msg); //impurevirtual函数
 int objectID ( ) const;//non-virtual函数
};

3)注意class设计者最常犯的两个错误:第一个错误时将所有函数声明为non-virtual。这使得derivedclasses没有余裕空间进行特化工作。注意80-20法则,平均而言你的函数调用中可以有80%virtual而不冲击程序的大体效率。所以应该放在举足轻重的20%代码上头。另一个常见错误时将所有成员函数声明为virtual。某些函数就是不该在derivedclass中被重新定义,果真如此你应该将那些函数声明为non-virtual。如果你的不变性(invariant)凌驾特异性(specialization),别害怕说出来。

 

条款35: 考虑virtual函数以外的其他选择(Consideralternatives to virtual functions)

本条款告诉程序员,当需要使用virtual函数时,可以考虑其他选择。Virtual函数的替代方案主要有:

1)使用non-virtualinterface(NVI)手法。思想是:将virtual函数放在private(protected)中,以publicnon-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设计模式中的一种表现形式),见下面代码。优点是每个对象拥有自己的函数实现,也可在运行时改变计算函数;缺点是:可能得降低类的封装性,否则非成员函数不能访问类中的non-public成员。

class GameCharacter;
intdefaultHealthCalc(constGameCharacter& gc);// defaultalgorithm

class GameCharacter{
public:
 typedef int(*HealthCalcFunc)(constGameCharacter&);
 explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)
  : healthFunc(hcf)
 { }
 int healthValue()const {
  return healthFunc(*this);
 }
 ...
private:
 HealthCalcFunc healthFunc;
};

3)以tr1::function成员变量替换virtual函数,这允许使用任何可调用物搭配一个兼容于需求的签名式(可以是函数、函数对象或成员函数)。这也是Strategy设计模式的某种形式。

4)继承体系内的virtual函数替换为另一个继承体系内的virtual函数。如下UML图所示:

每个GameCharacter对象都内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。这是Strategy设计模式的传统实现手法。优点是:可以随时添加新的算法。代码如下:

class GameCharacter;
class HealthCalcFunc{
public:
 ...
 virtual int calc(constGameCharacter& gc)const
 { ...}
 ...
};

HealthCalcFuncdefaultHealthCalc;

class GameCharacter{
public:
 explicit GameCharacter(HealthCalcFunc*phcf = &defaultHealthCalc)
  : pHealthCalc(phcf)
 { }
 int healthValue()const {
  return pHealthCalc->calc(*this);
 }
 ...
private:
 HealthCalcFunc *pHealthCalc;
};

 

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

绝不要重新定义继承而来的non-virtual函数,因为这是一种自相矛盾的设计。Non-virtual函数是用于同时指定函数接口和函数实现的,既然你想重新定义函数(也就是只继承函数接口),就应该定义为virtual的。

此外,本条款还涉及动态绑定(dynamicallybound)和静态绑定(staticallybound),举例如下:

//类的定义
class B{
public:
        void func(){ cout<<“B”;}
 virtual void func2(){ cout<<“B”;}
};

class D:public B{
public
 void func(){ cout<<“D”;}
 virtual void func2(){ cout<<“D”;}

};


//下面是对B和D的使用
DdObject;
B* bPtr =&dObject;
D* dPtr =&dObject;

//下面这两种调用方式:
bPtr->func(); //调用B::func
dPtr->func(); //调用D::func

bPtr->func2(); //调用D::func
dPtr->func2(); //调用D::func

解释:在C++继承中,virtual函数是动态绑定的调用的函数跟指针或者引用实际绑定的对象有关,而non-virtual函数是静态绑定的,调用的函数只跟声明的指针或者引用的类型相关

 

条款37: 绝对不要重新定义继承而来的缺省参数值(Neverredefine a function’s inherited default parameter value)

本条款限定在继承一个带有缺省函数的virtual函数,给出的建议是:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定所以,如果你重定义继承而来的virtual函数的缺省参数值,那么,你是用基类的缺省参数值来调用继承类的函数。

如果想重新定义一个继承而来的缺省参数值,可以考虑条款35virtual函数的替代方案,如NVI技术:令baseclasspublicnon-virtual函数调用privatevirtual函数,后者可被derivedclass重新定义。可以让non-virtual函数指定缺省参数,而privatevirtual函数负责真正的工作。代码如下:

class Shape
{
public:
 enum ShapeColor{RED,GREEN,BLUE};
    void draw(ShapeColor color= RED) const
 {
  doDraw(color);     //调用virtual函数
 }
private:
 virtual void doDraw(ShapeColor color)const = 0;//真正的工作,基类类需要重写
};

class Rectangle::publicShape
{
public:
 ....
private:
 virtual void doDraw(ShapeColor color)const;//不需指定缺省参数值
 ...
};

牢记的概念:绝不重新定义一个继承而来的缺省参数值,因为缺省参数是静态绑定的,而virtual函数----你唯一应该覆写的东西,是动态绑定的。为什么缺省参数是静态绑定而不是动态绑定呢?主要原因是运行效率。如果是动态绑定,程序员用起来很方便,但会降低运行效率,C++为了取舍,结果就是现在这样。

 

条款38:通过复合塑模出has-a根据某物实现出”(Model“has-a” or “is-implemented-in-terms-of” through composition)

复合的意义和public继承完全不同。复合(composition)表示某种类型对象内含其他类型对象。在应用域,复合意味着“has-a”,在实现域,复合意味is-implemented-in-terms-of

举了两个例子:

1、人有地址啊,电话号码啊,这个叫has-a

2、可以用一个list来实现一个set,这个叫根据某物实现

其实,没什么,我觉得2就像是适配器模式

 

条款39: 明智而审慎地使用private继承(Useprivate inheritance judiciously)

本条款主要介绍private继承的特点和使用场合。

1Private继承意味is-implemented-in-terms-of(根据某物实现出)。特点是:如果class之间的继承关系是private,编译器不会自动将一个derived class对象转化为一个base class对象;由privatebase class继承而来的所有成员,derived class中都会变成private属性,纵使它们在base class中原来是protectedpublic属性。

private继承表示根据某物实现。private继承意味着实现部分被继承,接口部分应略去(因为private继承完后都是private的,外界根本看不到)。如果Dprivate继承B,意思是D对象根据B对象实现而得,再没有其他含义了。

2private继承意味is-implemented-in-terms-of(根据某物实现),这个和条款38说的复合一致。大师指出:尽量用复合,只有当protected成员和/virtual函数牵扯进来的时候,还有就是空间方面的利害关系

主要有三种使用场合:两个并不存在is-a关系classes,其中一个需要访问另一个的protected成员;或需要重新定义其一个或多个virtual函数。另一种情况是,需要对emptyclasses的空间最优化,如下面的代码:

class Empty{}; //empty class

class HoldsAnyInt{
private:
 int x;
 Empty e;
};//sizeof(HoldsAnyInt)> sizeof(int)。Empty空对象需要安插一个char到空对象,并且有齐位需求。

class HoldsAnyInt::privateEmpty{
private:
 int x;
}; //sizeof(HoldsAnyInt)== sizeof(int),这个就是EBO(emptybased optimization)。
//实际应用中,类Empty中可以放入typedefs,enums,static成员变量,或non-virtual函数。STL中有很多例子。

 

条款40:明智而审慎地使用多重继承(Usemultiple inheritance judiciously)

本条款主要介绍多重继承的特点和使用场合。

1)多重继承比单一继承复杂,可能导致新的歧义性(同名时,不知道访问哪个基类的成员),以及对virtual继承的需要(任何派生类中的virtual基类总是用一个共享对象表示)。但是,Virtual继承会增加大小、速度、初始化复杂度等等成本。如果virtualbase classed不带任何数据,Virtual继承将是最具使用价值的情况。

2)多重继承的一个适用场合:“public继承某个Interfaceclass”“private继承某个协助实现的class”的两两组合。

考虑下面的情况:

classA {

}

classB : public A {

}

classC : public A {

}

classD : public B , public D{

}

 

第一个问题:

加入B,C中都有一个mf()函数,那么

Dd ;

d.mf(); //调用的是哪个?歧义

应该这么用:d.B::mf();  // Bmf

 

第二个问题:

A中的一份数据dataB也有了,C也有了,那么D中有几份?

缺省做法是2份,但可以通过virtual继承防止上面的事情发生

classB :  virtual public A {

}

classC : virtual public A {

}

那么是不是所有的继承都应该用virtualpublic呢?

不是,如果你用virtualpublic你必然要是靠编译器在背后默默支撑你,因此,代码的体积将增大,速度将变慢。并且,virtualbase class的初始化规则也复杂的多。

大师给了两个观点:

1、非必要不要用virtualbase

2、如果非要用,尽量不要在里面放置数据,这样不用担心初始化的事情。(和JAVAinterface差不多)

原创粉丝点击