Effective C++读书笔记 第六部分 继承与面向对象设计

来源:互联网 发布:行业分析怎么做 知乎 编辑:程序博客网 时间:2024/05/29 03:04

如果你了解C++各种特性的意义,你会发现,你对OOP的看法改变了。它不再是一项用来划分语言特性的仪典,而是可以让你通过它说出你对软件系统的想法。一旦你知道该通过它说些什么,移转至C++世界也就不再是可怕的高要求了。

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

    以C++进行面向对象编程,最重要的一个规则是:public inheritance(公有继承)意味is-a(是一种)的关系。

    如果你令class D以public形式继承class B,你便是告诉C++编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。你的意思是B比D表现出更一般化得概念,而D比B表现出更特殊化的概念。你主张:“B对象可派上用场的任何地方,D对象一样可以派上用场”,因为每一个D对象都是一种(是一个)B对象。反之如果你需要一个D对象,B对象无法效劳,因为虽然每个D对象都是一个B对象,反之并不成立。
     在C++领域中,任何函数如果期望获得一个类型为基类的实参(而不管是传指针或是引用),都也愿意接受一个派生类对象(而不管是传指针或是引用)。(只对public继承才成立。)
     好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。
     请记住:

  • “public继承”意味is-a。适用于base classes身上的每一件事情一定也使用于derived classes身上,因为每一个derived classes对象也都是一个base classes对象。
条款33:避免遮掩继承而来的名称

 C++的名称遮掩规则所做的唯一事情就是:遮掩名称。至于名称是否是相同或不同的类型,并不重要。即,只要名称相同就覆盖基类相应的成员,不管是类型,参数个数,都无关紧要。派生类的作用域嵌套在基类的作用域内。
     C++的继承关系的遮掩名称也并不管成员函数是纯虚函数,非纯虚函数或非虚函数等。只和名称有关。 
     如果你真的需要用到基类的被名称遮掩的函数,可以使用using声明式,引入基类的成员函数。
    请记住:

  • derived calsses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。   
  • 为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding function)。
条款34:区分接口继承和实现继承

表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承函数实现继承

  • 成员函数的接口总是会被继承。
  • 声明一个纯虚函数的目的是为了让派生类只继承函数接口
  • 声明一个虚函数的目的是让派生类继承该函数的接口和缺省实现
  • 声明一个非虚函数的目的是为了令派生类继承函数的接口及一份强制性实现

    请记住:

  • 接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
  • pure virtual函数只具体制定接口继承。
  • 简朴的(非纯)impure virtual函数具体制定接口继承及缺省实现继承。
  • non-virtual函数具体制定接口继承以及强制性实现继承。
条款35:考虑virtual函数以外的其它选择

本条款告诉程序员,当需要使用virtual 函数时,可以考虑其他选择。

Virtual函数的替代方案是:
(1) 使用non-virtual interface(NVI)手法。思想是:将virutal函数放在private中,而在public中使用一个non-virtual函数调用该virtual函数。优点是:用一个不能被子类重定义的函数,做一些预处理、后处理等,子类只需要在private中重新实现virtual函数即可。即:基类给出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设计模式中的一种表现形式)。优点是对象实例和派生类对象,可使用各种实现,也可在运行时随意改;缺点是:该函数不能访问类中的私有成员
举例:

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

(3) 以tr1::function成员变量替换virtual函数,这允许使用任何可调用物搭配一个兼容于需求的签名式。这也是Strategy设计模式的某种形式。这种方式比上面的函数指针更灵活、限制更少:[1]返回值不一定是int,与其兼容即可; [2]可以是function对象; [3]可以是类的成员函数。
(4) 继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现手法。这种方式最大的优点是:可以随时添加新的算法。举例:

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

 请记住:

  • virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
  • 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
  • tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)。   
 条款36:绝不重新定义继承而来的non-virtual函数

本条款告诫程序员:绝不要重新定义继承而来的non-virtual函数,因为这不仅容易造成错误,而且是一种自相矛盾的设计。

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

该条款告诫程序员:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数-你唯一应该覆写的东西-却是动态绑定。

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

  • 复合(composition)的意义和public继承完全不同。
  • 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。    
39、使用private继承

(1)如果class之间的继承关系是private。编译器不会自动将一个derived class对象转化为一个base class对象。由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原来是protected或public属性。
(2)private继承意味is-implemented-in-terms-of,它的级别比组合低,当derived class需要protected base class或者需重新定义继承而来的virtual class时,设计才是合理的。
(3)与复合不同 ,private继承可以使empty base空间最优化。举例:

class Empty{}; //empy class
 
clsss HoldsAnyInt{ 
 private: 
  int x; 
  Empty e; 
};//这个的大小为>sizeof(int),Empty空对象需要安插一个char到空对象,并且有齐位需求。
 
class HoldsAnyInt::private Empty{ 
 private: 
  int x; 
}; //这个sizeof大小为sizeof(int)
 
补充:
 
class HoldsAnyInt::private Empty{ 
 private: 
  int cal() = 0; 
  int x; 
}; //这个sizeof大小为8, 实际上为size(int) + sizeof(vptr)

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

(1) 多重继承比单一继承复杂。他可能导致新的歧义性,以及virtual继承的需要
(2) Virtual继承会增加大小、速度、初始化复杂度等等成本。如果virtual base classed不带任何数据,将是最具使用价值的情况。
(3) 多重继承最正当用途是:其中一个设计“public 继承某个interface class”和“priavte继承某个协助实现的class”的两相结合。



原创粉丝点击