Effective C++ 6 继承与面向对象设计 学习记录

条款32确定public继承is-a的意义符合你的设计意图

Class Person;

Class Student : public Person{};

Void study(const  Student &s);

Void eat(const Person &p);

Person p;

Student s;

eat(p);

eat(s);

Study(p);

Study(s);

Public继承代表的是is-a的关系。对于eat()函数,编译器可以将Student对象转型为Person对象,而对于study()函数而言,Person对象不一定是Student对象,因此反过来就行不通。如果想要程序编译通过,可能要进行强制类型转换。

合理的设计思维

Public继承意味着父类对象上可以实施的任何操作,子类对象必须也可以实施。因为每个子类对象都包括一个父类对象。

条款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();

}

Derived d;

Int x;

d.mf1();

d.mf1(x);

d.mf2();

d.mf3();

d.mf3(x);

对于mf3()函数是子类non-virtual函数覆盖了父类non-virtual函数,条款36有详细的解释。这里我们主要讨论子类定义覆盖父类定义的现象。由于覆盖现象导致父类一些定义不再可见,从而导致错误。这与public继承is-a的意义相左。子类public继承与父类,却无法使用父类public成员函数。

合理的设计思维

public继承会覆盖父类内的名称,有时这会违反public的意义。可以采用using显示申明需要可见的父类成员函数。

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

Class Shape

{

 Public:

 Virtual void draw()cosnt=0;

 Virtual void error(const string &msg);

 Int objectID() const

};

Class Rectangle: public Shape{};

Class Ellicpse: public Shape{};

首先需要明确的是接口继承与实现继承是不同的概念,pure virtual函数是可以有实现的。上述类定义中,对于pure virtual函数,当子类public继承的时候,要求一定要有自己的实现。因此,声明一个pure virtual函数的目的是为了继承接口;对于声明impure virtual函数的目的是为了继承接口和缺省实现;对于声明non-virtual函数的目的是为了继承接口和强制实现,不容更改定义。

合理的设计思维

Public继承下,子类总是继承父类的接口。因此,是否需要继承实现、重新实现还是不容改变就决定了函数声明的方式,是pure virtualimpure virtual还是non-virtual

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

1. 如条款37所述的NVI设计方法

案列见条款37,我们需要明白一点,NVI设计方法并不一定需要virtual函数为private。如果需要在外覆器wrapper中调用基类的对应兄弟,则可以将其声明为protectedVirtual析构函数的声明一定要是public,详细见条款7NVItemplate method设计模式的一个独特表现形式。

2. non-member函数替代

Class GameCharacter;

Int defaultHealthCalc(const GameCharacter &gc);

Class GameCaracter

{

 Public

 Typedef int (*HealthCalcFunc)(const GameCaracter &);

 Explicit GameCaracter(HealthCalcFunc hcf=defaultHealthCalc): healthFunc(hcf){}

 Int healthValue()const

 {

  Return healthFunc(*this);

 }

 Private:

 HealthCalcFunc healthFunc;

};

Class EvilBadGuy: public GameCharater

{

 Public:

 Explicit EvilBadGuy(HealthCalcFunc hcf=defualtHealthCalc): GameCharacter(hcf){}

};

Int loseHealthQuickly(const GameCharacter &);

Int loseHealthSlowly(const GameCharacter &);

EvilBadGuy ebg1(loseHealthQuickly);

EvilBadGuy ebg2(loseHealthSlowly);

此时,类中定义了一个指向函数的指针变量。对于同一类实体,现在可以调用不同健康计算函数,因而更加灵活。同时,如果在类中再定义一个可以设置指针变量的函数SetHealthCalcFunc,那么对象的健康计算方式可以在运行期进行改变。但这种设计方式也有缺点,比如健康计算函数需要使用到类中privateprotected成员变量和成员函数,而此时并没有提供这些接口,那么就存在实现上的不便。此时,可以弱化类的封装,将这个健康计算函数声明为其friend函数,或者为能够使用类中成员变量或函数提供public接口。此种设计方法是strategy设计模式的简单应用。

3. 借助tr1::function完成strategy模式

可以借助templates,来实现上述借由函数指针实现的strategy模式。

Class GameCharacter;

Int defaultHealthCalc(const GameCharacter &gc);

Class GameCaracter

{

 Public

 Typedef  std::tr1::function<int (const GameCharacter &)> HealthCalcFunc;

 Explicit GameCaracter(HealthCalcFunc hcf=defaultHealthCalc): healthFunc(hcf){}

 Int healthValue()const

 {

  Return healthFunc(*this);

 }

 Private:

 HealthCalcFunc healthFunc;

};

4. 经典strategy模式

Class GameCaracter;

Class HealthCalcFunc

{

 Public:

 Virtual int calc(const GameCharacter &gc) const

 {

 } 

};

HealthCalcFunc defaultHealthCalc;

Class GameCaracter

{

 Public

 Explicit GameCaracter(HealthCalcFunc *hcf=&defaultHealthCalc): healthFunc(hcf){}

 Int healthValue()const

 {

  Return healthFunc->calc(*this);

 }

 Private:

 HealthCalcFunc *healthFunc;

};

此时,可以通过为HealthGalcFunc类添加子类,并在覆盖calc()函数,就可以实现不同的健康计算方式。

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

Class B

{

 Public

 Void mf();

};

Class D: public B

{

 Public:

 Void mf();

};

D继承B,且D覆盖了Bnon-virtual成员函数。

D x;

B *pB=&x

D *pD=&x;

pB->mf();

pD->mf();

对于non-virtual成员函数(此时静态类型和动态类型之分),编译器实施静态绑定。因此,对象D表现怎么得行为取决于指向该对象的指针类型,而非对象本身。

合理的设计思维

1.public继承(public继承是is-a的关系);

2.定义virtual成员函数。

因此,此条款对于条款7也是一个合理的解释。

 

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

Class Shape

{

 Public:

 Enum ShapColor{Red  Green  Blue};

 Virtual void draw(ShapeColor color=Red) const=0;

};

Class Rectangle:Public Shape

{

 Public:

 Virtual void draw(ShapeColor color=Green) const=0;

};

Class Circle:Public Shape

{

 Public:

 Virtual void draw(ShapeColor color) const=0;

};

virtual 函数不同,virtual 函数缺省参数值采用静态绑定,因此子类对象调用动态绑定的virtual函数时,缺省参数值却由父类指定。 

Shape *ps=new Rectangle;

Ps->draw();

Rectangle中虽然修改了缺省的参数值,但draw()的缺省参数值还是Red,并没有符合设计者的预期效果。

Circle c;

c.draw(Shape::Red);

Shape *ps=new Circle ;

Ps->draw();

Circlevirtual函数声明与Shape不一致。当对象调用draw()时,静态绑定下的该函数不会从父类继承缺省参数值,因此需要显示提供。而以指针调用draw()时,动态绑定下的该函数会从父类继承缺省参数值。

合理的设计思维NVIno virtual interface

Class Shape

{

 Public:

 Enum ShapColor{Red  Green  Blue};

 Virtual void draw(ShapeColor color=Red) const

  {

    doDraw(color);

  }

Private:

 Virtual void doDraw(Shape color) const;

};

Class Rectangle:public Shape

{

Private:

 Virtual void doDraw(Shape color) const;

};

此时,draw()是non-virtual成员函数。由条款36可知,其不应该被覆盖,缺省参数设置与成员函数virtual性再无关系。也可以看到父类、子类中doDraw()函数声明一致。

 

条款38:由聚合实现has-a 或 is-implemented-in-terms-of的类间关系

1. has-a:

Class Address;

Class PhoneNumber;

Class Person

{

 Private:

 Std::string name;

 Address address;

 PhoneNumber phonenumber;

};

2. is-implemented-in-terms-of

Template<class T>

Class Set

{

 Public:

 Bool member(const T& item) const; //行为

 Void insert(const T& item);

 Void remove(const T& item);

 Std::size_t size() const;

 Private:

 Std::list<T> rep;

};

has-a 强调“拥有……”的概念;is-implemented-in-terms-of强调“由……实现”的概念,而又不是is-a的概念,必须重新定义一些行为

合理的设计思维

聚合(composition)的意义与public继承(is-a)完全不同。在设计类时,要明确类间关系,从而确定设计方法。

条款39:理解private继承

Private继承承载着聚合中is-implemented-in-terms-of的概念。

Class Person;

Class Student : private Person{};

Void eat(const Person &s);

Person p;

Student s;

eat(p);

eat(s);

对于public继承,我们知道它是is-a的关系。此例表明private继承不是is-a的关系,因为子类Student对象s作为参数调用eat()时,编译器并没有将s转换成Person对象,而public继承却会这么做。所以private继承只代表实现技术手段而非反应对象间的关系。

Private继承存在的必要性有三个因素:

1. protectedpublic成员

private继承会使父类所有数据成员的访问权限变为private,不可见性避免了使用者一些不恰当的访问操作。

2.继承并重新定义virtual函数,但又不是is-a的关系

此时,如果采用public继承,明显不合时宜。通过private继承将virtual函数变为private,避免使用者调用,因为向使用者提供这样的接口并不合适。如有需要可重新定义virtual函数,但必须声明为private,理由很显然。

3.EBO(空白基类最优化)

Class Empty{};

Class HoldsAnInt

{

 Private:

 Int x;

 Empty e;

};

空类以为着这个类中没有virtual函数,没有non-static成员变量,也就意味着sizeofEmpty=0;然而,实际情况是编译器至少会给这样的空类对象分配一个char型大小的空间。因此,对于4四节对齐的系统,sizeofHoldsAnInt=8

Class HoldsAnInt : private Empty

{

 Private:

 Int x;

};

此时,HoldsAnInt private继承Empty,可以确定sizeofHoldsAnInt =sizeofInt)。看以看到,编译器此时不会再为对象HoldsAnInt 分配一个char型大小的空间。和上面的相比,对象占用空间自然少。然而,空类本身就很特殊,所以不值得提倡。

合理的设计思维

尽量使用聚合来表达is-implemented-in-terms-of这种概念。

条款40:多重继承

1.多重继承容易造成歧义

Class BorrowalbleItem

{

 Public:

 Void chechout();

};

Class ElectronicGadget

{

 Private:

 Bool checkout() const;

};

Class MP3Player: public BorrowableItem , public ElectronicGadget

{

};

MP3Player mp;

Mp.checkout();

Mp.BorrowableItem ::checkout();

此时,编译器根据最佳匹配原则寻找最佳函数版本,然而两个函数匹配程度一致,导致歧义。编译器匹配函数主要原则有函数名、参数、返回值和作用域,而此时两个函数只是访问权限的区别,因而需要指明具体的调用函数。

2.多重继承容易造成代码重复

Class File{};

Class InputFile : public File{};

Class OutputFile :public File{};

Class InputFile : virtual public File{};

Class OutputFile :virtual public File{};

Class IOFile :public InputFile, public OutputFile{};

此时,IOFile 多重继承InputFileOutputFile,而两者都是继承自File。因此,File的构造函数会被调用两次,IOFile 对象会有两份File成员变量数据。这种情况下,C++引入了虚继承机制。虚继承机制下,多重继承时,子类对象中父类成员变量数据只会有一份。但为了达到这个目的,编译器会做很多工作,对象所占的内容空间也较非虚继承类对象大,变量访问速度也较慢。可以猜想,编译器的大部分工作在于判断和识别继承树中类的virtual性,以及virtual类成员初始化等工作上。

3. 多重继承可以实现public继承自某接口,private继承自某实现

Class IPerson

{

 Virtual ~IPerson();

 Virtual string name() const =0;

 Virtual string birthDate() const=0;

};

Class DatabaseID{};

Class PersonInfo

{

 Explicit PersonInfo(DatabaseID pid);

 Virtual ~PersonInfo();

 Virtual const char* theName() const;

 Virtual const char* theBirthDate() const;

 Virtual const char* valueDelimOpen() const;

 Virtual const char* valueDelimClose() const;

};

Class CPerson : public IPerson, private PersonInfo

{

 Public:

 explicit CPerson(DatabaseID pid):PersonInfo(pid){}

 Virtual string name() const //继承某接口

{

 Return PersonInfo::theName();//继承某实现

}

 Virtual string birthDate() const

{

 Return PersonInfo::theBirthDate();

}

 Private:

 Const char* valueDelimOpen() const 

{

 Return “”;

}

 Const char* valueDelimClose() const 

{

 Return “”;

}

};

Public继承是is-a的关系,private继承是聚合的概念。合理的运用不同的继承方式,可以使得代码简洁、合理、易于维护。

合理的设计思维

如无必要,尽量使用单继承方法,因为在使用多重继承时,必须考虑歧义和共同继承问题,同时还要考虑多重继承的效率问题。尽量避免虚基类带有任何数据成员。当然,多重继承也有其一定的优越性。上述3反应了在进行类设计时,合理安排多重继承的方式,可以使得类结构简洁且合理。

 

原文地址:https://www.cnblogs.com/alpha19881007/p/20130807_byalpha.html