Effective C++ 读书笔记(六)

6  继承与面向对象设计

条款32:确定你的public继承塑模出is-a关系

“public继承”意味is-a。适用于base classes身上的每一件事一定也适用于derived classes上,因为每一个derived classes对象也都是一个base class对象。

 

条款33:避免遮掩继承而来的名称

int x;

void someFunc()

{

double x;

cin>>x; //尽管someFunc的x是double,global x是int,但不要紧。C++名称遮掩规则仍会遮掩名称。即类型是否相同不重要

}

 

 

class Base{

public:

         virtual void mf1()=0;

         virtual void mf1(int)

{cout<<"Base::mf1(int)"<<endl;}

         virtual void mf2()

{cout<<"Base::mf2()"<<endl;}

         void mf3()

{cout<<"Base::mf3()"<<endl;}

         void mf3(double)

{cout<<"Base::mf3(double)"<<endl;};

};

class Derived:public Base{

public:

         //using Base::mf1;

         //using Base::mf3;

         virtual void mf1()

{cout<<"Derived::mf1()"<<endl;}

         void mf3()

{cout<<"Derived::mf3()"<<endl;}

         void mf4()

{cout<<"Derived::mf4()"<<endl;}

};

 

         Derived d;

         int x=0;

         d.mf1();

         d.mf1(x);

//error, “Derived::mf1”: 函数不接受1 个参数

         d.mf2();

         d.mf3();

         d.mf3(x);

//error, “Derived::mf3”: 函数不接受1 个参数

Derived::mf1遮掩了Base::mf1, Derived::mf3遮掩了Base::mf3。

以作用域为基础的“名称遮掩规则”即使base classes和derived classes内的函数有不同的参数类型也适用,而且不论是virtual或non-virtual。

这些行为背后的原理是防止你在程序库或应用构架内建立新的derived  classes时附带地从疏远的base classes继承重载函数。

 

将注释的代码去掉,即可通过编译,即让Base class内名为mf1和mf3的所有东西在Dervied 作用域内可见。

 

 

           继承方式

基类属性

 

public

 

protected

 

private

公有继承(public)

public

protected

不可见

保护继承(protected)

protected

protected

不可见

私有继承(private)

private

private

不可见

对于私有继承,继承后,派生类在自己的内部依旧可以访问其基类的public和protected方法,但在外部访问该派生类时不能访问其基类的public方法了,因为已经变成私有的了。

条款34:区分接口继承和实现继承

class Shape{

pubilc:

         virtual void draw( ) const = 0; //pure virtual函数

         virtual void error( const std::string & msg ); //impure virtual (不纯的 虚函数)

         int objectID ( ) const; //non-virtual 非虚函数

};

class Rectangle: public Shape{ … }

class Ellipse: public Shape { … }

n   pure virtual函数有两个突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。声明一个pure virtual函数的目的是为了让derived class只继承函数接口。(可以为pure virtual函数提供一份实现代码,但调用时必须明确指出其class名称)。

Shape* ps = new Shape; //Error, shape是抽象的

Shape* ps1 = new Rectangle;  ps1->draw();

Shape* ps2 = new Ellipse;  ps2->draw();

ps1->Shape::draw(), ps2->Shape::draw();

n   impure virtual函数必须提供一份实现代码,derived class可能覆写(override它。声明impure virtual函数的目的是为让derived class继承函数的接口和缺省实现。

n   non-virtual函数表示它不打算在derived class中有不同的形为。

 

条款35:考虑virtual函数以外的其他选择

class GameCharacter

{

public:

         int healthValue( ) const

         {        …

                   int retVal = doHealthValue ( );

                   …

                   return retVal;

}

private:

         virtual int doHealthValue( ) const

         { …

         }

}

n   藉由Function Pointers实现Strategy模式

class GameCharacter;

int defaultHealthCalc ( const GameCharacter& gc);

class GameCharacter{

public:

         typedef int (*HealthCalcFunc) (const GameCharacter&);

         explicit GameCharacter (HealthCalFunc  hcf = defaultHealthCalc) : healthFunc (hcf){}

         int healthValue( ) const {return healthFunc (*this);}

private:

         HealthCalcFunc  healthFunc;

};

 

class EvilBadGuy : public GameCharacter{

public:

         explicit EvilBadGuy ( HealthCalcFunc  hcf = defaultHealthCalc ): GameCharacter (hcf) {…}

};

int loseHealthQuickly ( const GameCharacter&);

int loseHealthSlowly (const GameCharacter& );

EvilBadGuy ebg1(loseHealthQuickly ), ebg2(loseHealthSlowly );

 

n   古典的Strategy模式

class GameCharacter;

class HealthCalcFunc{

public: …

         virtual int calc (const  GameCharacter& gc)const { .. }

         …

};

HealthCalcFunc  defaultHealthCalc;

class GameCharacter{

public:

         explicit GameCharacter (HealthCalFunc  phcf = &defaultHealthCalc) : pHealthFunc (phcf){ }

         int healthValue( ) const {return pHealthFunc ->calc(*this);}

private:

         HealthCalcFunc*  pHealthFunc;

};

class SlowHealthLoser: public HealthCalcFunc{ }

class FastHealthLoser: public HealthCalcFunc{ }

 

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

class B{

public:

         void mf();

         …

};

class D: public B{

public:

         void mf(); //遮掩了B::mf

         …

};

D x;

B* pB = &x;

pB->mf( ); //B::mf

D* pD = &x;

pD->mf( );//D::mf

non-virtual函数都是静态绑定。由于pB是一个pointer-to-B,通过pB调用的non-virtual永远是B所定义的版本,即使pB指向一个类型派生于B 的类型。

 

条款37:绝不重新定义继承而来的缺省参数值

class Shape{

public:

enum ShapeColor {Red, Green, Blue};

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:

virtual void draw (ShapeColor color )  const ;

//当客户以对象调用此函数,必须指定参数值

};

Shape* ps; //静态类型为Shape*,没有动态类型

Shape* pc = new Circle; //静态类型为Shape*,动态类型为Circle*

Shape* pr = new Rectangle; //静态类型为Shape*,动态类型为Rectangle*

ps = pc//动态类型为Circle*

ps = pr//动态类型为Rectangle*

pc->draw(Shape::Red); //调用Circle::draw(Red)

pr->draw(Shape::Red);//调用Rectangle::draw(Red)

pr->draw( ); //调用Rectange::draw(Red)

Rectange::draw缺省参数值是Green。原因:pr的静态类型为Shape*,所以此一调用的缺省参数值来自Shape class而非Rectangle class!

 

替代方法:

class Shape{

public:

enum ShapeColor {Red, Green, Blue};

void draw (ShapeColor color=Red) const

{

doDraw(color);

}

private:

virtual void doDraw (ShapeColor color) const = 0;

};

class Rectangle: public Shape{

public:

private:

virtual void doDraw (ShapeColor color) const;

};

 

条款38:通过复合塑模出has-a或“根据某物实现出”

STL set的底层实现是平衡查找树,但每个元素需要的开销比较大。如果空间比速度重要时,能否采用其他设计?例如可以采用底层的linked list。

template<typename T>

class set:public std::list<T>{ … }//错误做法,公有继承,即is-a关系。如果D是一种B,那么对B为真的每一件事对D也都应该为真。但list可以包含重复元素,set不可以。因此不是is-a关系。但set可以根据list实现出来。因此是has-a的关系。

template<typename T>

class set{

private:  std::list<T>  rep;

}

 

条款39:明智而审慎地使用private继承

条款32认证过C++如何将public继承视为is-a关系。其中,class Student以public形式继承class Person,于是编译器在必要时刻将Student暗自转换为Person。但对于以下情形:

class Person { … }

class Student : private Person { … }

void eat( const Person&  p);

Person  p;

Student  s;

eat ( p ); //OK

eat( s ); //error, 对于private继承,编译器不会自动将一个derived class对象转换为一个base class对象。

另外,由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原本是protectedpublic属性。

         如果你让class D以private形式继承class B,你的用意是为了采用class B内已经备妥的某些特性,不是因为B对象和D对象存在有任何观念上的关系。

         Private继承意味is-implemented-in-terms-of(根据某物实现出),复合的意义也是这样。如何取舍?答案很简单:尽可能使用复合,必要时才使用private继承。

        

         激进情况涉及空间最优化,可能会促使你选择“private继承”而不是“继承加复合”。这种激进情况只适用于你所处理的class不带任何数据时,这样的classes没有non-static成员变量,没有virtual函数,也没有virtual-base-class。例如:

class Emtpy { } //没有数据,所以其对象应该不使用任何内存

class HoldsAnInt {

private:

         int  x;

         Empty  e; //应该不需要任何内存,但在大多数编译器中sizeof(Emtpy) =1。面对大小为0的独立对象,通常C++官方勒令默默安插一个char到空对象内。然而齐位需求可能使得编译器为HoldsAnInt这样的class加上一些衬垫。

};

Class HoldsAnInt : private Emtpy {

private: int x;

};

几乎可以确定sizeof(HoldsAnInt) = sizeof(int). 这就是所谓的空白基类最优化EBO。(若客户非常在意空间,那么值得注意EBO。另外EBO一般只在单一继承下才可靠)

现实中的empty class并不真的是 empyt。虽然它们从末拥有non-static成员变量,但往往内含typedefs, enums, static成员变量,或non-virtual函数。

         当你面对并不存在is-a关系 的两个classes,其中一个需要访问另一个的protected成员,或需要重新定义其一或多个virtual函数,private继承极有可能成为正统设计策略。

 

条款40:明智面审慎地使用多重继承

class BorrowItem{

public:

void  checkOut( );

};

class ElectronicGadget{

private:

void  checkOut( );

};

class MP3Player:

public BorrowItem,

public ElectronicGadget

{…

};

MP3Player mp;

mp.checkOut( );//歧义,尽管checkOut只有BorrowItem类的可用(ElectronicGadget的checkOut私有)。这与C++解析重载函数调用的规则相符:在看到是否有函数可用之前,C++首先确认这个函数对此调用是否最佳匹配。找出最佳匹配之后才检验其可用性

         使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base class的成员变量,也比访问non-virtual base class的成员变量慢。另外,支配virtual base class初始化的规则比non-virtual base的情况复杂且不直观 。

         忠告:(1)非必要不使用virtual bases。平常使用non-virtual继承。(2)如果必须使用virtual base class,尽可能避免在其中放置数据。这样就不用担心这些classes身上的初始化和赋值带来的诡异事情了。

 

posted on 2012-11-21 22:41  ArcherXu  阅读(155)  评论(0编辑  收藏  举报

导航