Effective C++摘要《第6章:继承和面向对象设计》20090210

来源:互联网 发布:mac为女性推出 编辑:程序博客网 时间:2024/06/05 16:48

===条款35: 使公有继承体现 "是一个" 的含义===
当写下类D("Derived" )从类B("Base")公有继承时,你实际上是在告诉编译器(以及读这段代码的人):类型D的每一个对象也是类型B的一个对象,但反之不成立;
class Student: public Person { ... };
在C++世界中,任何一个其参数为Person类型的函数(或指针或引用)可以实际取一个Student对象(或指针或引用):
void dance(const Person& p);        // 任何人可以跳舞
void study(const Student& s);       // 只有学生才学习
公有继承声称:对基类对象适用的任何东西 ---- 任何!---- 也适用于派生类对象。
===条款36: 区分接口继承和实现继承===
class Shape {
public:
  virtual void draw() const = 0;//纯虚
  virtual void error(const string& msg);//虚
  int objectID() const;//非虚
  ...
};
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。(注:基类为一个纯虚函数提供定义也是可能的)
  特点:纯虚函数必须在子类中重新声明、可以在基类中有自己的实现
声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。
声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。
理解了纯虚函数、简单虚函数和非虚函数在声明上的区别,就可以精确地指定你想让派生类继承什么:仅仅是接口,还是接口和一个缺省实现?或者,接口和一个强制实现?
===条款37: 决不要重新定义继承而来的非虚函数===
class D: public B {};
· 适用于B对象的一切也适用于D对象,因为每个D的对象 "是一个" B的对象。
· B的子类必须同时继承mf的接口和实现,因为mf在B中是非虚函数。
===条款38: 决不要重新定义继承而来的缺省参数值===
本条款的理由就变得非常明显:虚函数是动态绑定而缺省参数值是静态绑定的。
enum ShapeColor { RED, GREEN, BLUE };
// 一个表示几何形状的类
class Shape {
public:
  // 所有的形状都要提供一个函数绘制它们本身
  virtual void draw(ShapeColor color = RED) const = 0;
  ...
};
class Rectangle: public Shape {
public:
  // 注意:定义了不同的缺省参数值 ---- 不好!
  virtual void draw(ShapeColor color = GREEN) const;
  ...
};
Shape *pr = new Rectangle;      // 静态类型 = Shape*

pr->draw();//缺省值将是RED!
===条款39: 避免 "向下转换" 继承层次===
从一个基类指针到一个派生类指针 ---- 被称为 "向下转换"
===条款40: 通过分层来体现 "有一个" 或 "用...来实现"===
使某个类的对象成为另一个类的数据成员,从而实现将一个类构筑在另一个类之上,这一过程称为 "分层"(Layering)
分层的含义是 "有一个" 或 "用...来实现"。
1、有一个
class Address { ... };           // 某人居住之处
class Person {
public:
  ...
private:
  string name;                   // 下层对象
  Address address;               // 同上
};
Person类被认为是置于Address类的上层
上面的Person类展示了 "有一个" 的关系。
Person有一个Address
2、用...来实现
假设需要一个类模板,用来表示任意对象的集合,并且集合中没有重复元素。
// Set中错误地使用了list
template<class T>
class Set: public list<T> { ... };
正如条款35所说明的,如果D "是一个" B,对B成立的所有事实对D也成立。但是,list对象可以包含重复元素,相反,Set不可以包含重复元素
因为这两个类的关系并非 "是一个",所以用公有继承来表示它们的关系就是一个错误
// Set中使用list的正确方法
template<class T>
class Set {
public:
  void insert(const T& item);
  void remove(const T& item);
private:
  list<T> rep;                       // 表示一个Set
};
Set用list来实现
===条款41: 区分继承和模板===
模板类的特点:行为不依赖于类型(函数实现方式跟类型无关)
· 当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类。
· 当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类。
===条款42: 明智地使用私有继承===
class Person { ... };
class Student:                      // 这一次我们
  private Person { ... };           // 使用私有继承

void dance(const Person& p);        // 每个人会跳舞
void study(const Student& s);       // 只有学生才学习

Person p;       // p是一个人
Student s;     // s是一个学生
dance(p);     // 正确, p是一个人
dance(s);    // 错误!一个学生不是一个人,这里编译会错误,从“Student *__w64 ”到“const Person &”的转换存在,但无法访问
私有继承的第一个规则:
和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如Student)转换成基类对象(如Person)。
第二个规则:
从私有基类继承而来的成员都成为了派生类的私有成员,即使它们在基类中是保护或公有成员。
这为我们引出了私有继承的含义:私有继承意味着 "用...来实现"。
如果使类D私有继承于类B,这样做是因为你想利用类B中已经存在的某些代码,而不是因为类型B的对象和类型D的对象之间有什么概念上的关系。因而,私有继承纯粹是一种实现技术。
私有继承在软件 "设计" 过程中毫无意义,只是在软件 "实现" 时才有用。
私有继承意味着 "用...来实现" ,"分层" 也具有相同的含义,怎么在二者之间进行选择呢?
答案很简单:尽可能地使用分层,必须时才使用私有继承。
在有虚函数要被重新定义的情况下,需要使用私有继承
===条款43: 明智地使用多继承===
1、二义性
class Lottery {
public:
  virtual int draw();
};

class GraphicalObject {
public:
  virtual int draw();
};

class LotterySimulation: public Lottery,
                         public GraphicalObject {
  ...                          // 没有声明draw
};
class SpecialLotterySimulation: public LotterySimulation {
public:
  virtual int draw();
};

LotterySimulation *pls = new SpecialLotterySimulation;
pls->draw();                   // 错误! ---- 二义
pls->Lottery::draw();          // 正确
pls->GraphicalObject::draw();  // 正确
问题:
在这种情况下,即使pls指向的是SpecialLotterySimulation对象,也无法调用这个类中定义的draw函数。
Lottery和GraphicalObject中的draw函数都被声明为虚函数,所以子类可以重新定义它们,但如果LotterySimulation想对二者都重新定义那该怎么办?没办法
解决办法:
class AuxLottery: public Lottery {
public:
  virtual int lotteryDraw() = 0;
  virtual int draw() { return lotteryDraw(); }
};

class AuxGraphicalObject: public GraphicalObject {
public:
  virtual int graphicalObjectDraw() = 0;
  virtual int draw() { return graphicalObjectDraw(); }
};

class LotterySimulation: public AuxLottery, public AuxGraphicalObject {
public:
  virtual int lotteryDraw();
  virtual int graphicalObjectDraw();
};
这两个新类,AuxLottery和AuxGraphicalObject,本质上为各自继承的draw函数声明了新的名字。新名字以纯虚函数的形式提供,本例中即lotteryDraw和graphicalObjectDraw;函数是纯虚拟的,所以具体的子类必须重新定义它们。另外,每个类都重新定义了继承而来的draw函数,让它们调用新的纯虚函数。最终效果是,在这个类体系结构中,有二义的单个名字draw被有效地分成了无二义但功能等价的两个名字:lotteryDraw和graphicalObjectDraw:
LotterySimulation *pls = new LotterySimulation;
Lottery *pl = pls;
GraphicalObject *pgo = pls;
// 调用LotterySimulation::lotteryDraw
pl->draw();
// 调用LotterySimulation::graphicalObjectDraw
pgo->draw();

2、虚基类
                       A 
                       //
                      /  /
                     /    /
                    B    C
                     /    /
                      /  /
                       //
                       D
A是非虚基类时D对象通常的内存分布:

           A部分+ B部分+ A部分 + C部分 + D部分

A是虚基类时D对象在某些编译器下的内存分布:

                 ------------------------------------------------
                 |                                                   |
                 |                                                  +
B部分 + 指针 + C部分 + 指针 + D部分 + A部分
                                            |                       +
                                            |                        |
                                            ------------------------
  a、谁向虚基类传递构造函数参数
  b、虚函数的优先度:虚基类中虚函数的调用优先级低于非虚函数的中的虚函数

3、设计
尽量用单继承
===条款44: 说你想说的;理解你所说的===
公有继承:如果D从B公有继承,类型D的每一个对象也 "是一个" 类型B的对象
将成员函数声明为非虚函数会给子类带来限制,声明一个非虚成员函数,你实际上是在说这个函数表示了一种特殊性上的不变性
· 共同的基类意味着共同的特性。如果类D1和类D2都把类B声明为基类,D1和D2将从B继承共同的数据成员和/或共同的成员函数。见条款43。
· 公有继承意味着 "是一个"。如果类D公有继承于类B,类型D的每一个对象也是一个类型B的对象,但反过来不成立。见条款35。
· 私有继承意味着 "用...来实现"。如果类D私有继承于类B,类型D的对象只不过是用类型B的对象来实现而已;类型B和类型D的对象之间不存在概念上的关系。见条款42。
· 分层意味着 "有一个" 或 "用...来实现"。如果类A包含一个类型B的数据成员,类型A的对象要么具有一个类型为B的部件,要么在实现中使用了类型B的对象。见条款40。
下面的对应关系只适用于公有继承的情况:
· 纯虚函数意味着仅仅继承函数的接口。如果类C声明了一个纯虚函数mf,C的子类必须继承mf的接口,C的具体子类必须为之提供它们自己的实现。见条款36。
· 简单虚函数意味着继承函数的接口加上一个缺省实现。如果类C声明了一个简单(非纯)虚函数mf,C的子类必须继承mf的接口;如果需要的话,还可以继承一个缺省实现。见条款36。
· 非虚函数意味着继承函数的接口加上一个强制实现。如果类C声明了一个非虚函数mf,C的子类必须同时继承mf的接口和实现。实际上,mf定义了C的 "特殊性上的不变性"。见条款36。

 

 

原创粉丝点击