Effective C++(六)继承与面向对象设计

vitual函数意味“接口必须被继承”,non-virtual函数意味“接口和实现都必须被继承”

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

    • 请记住:“public继承”意味is-a。适用于base classes身上的每一件事情一定也适用于derived classes身上,因为每一个derived class对象也都是一个base classes对象
    • is-a并非是唯一存在于classes之间的关系。另两个常见的关系是has-a(有一个)和is-implemented-in-terms-of(根据某物实现出)。
  2. 条款33:避免遮掩而来的名称

    • 请记住:
      • derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
      • 为了让被遮掩的名称再见天日,可使用using声明式或转交函数
    • C++的名称遮掩规则所做的唯一事情就是:遮掩名称。置于名称是否应和相同或不同的类型,并不重要。
    • derived class作用域被嵌套在base class作用域内
      Alt text
      Alt text
    • 编译器在寻找变量定义的做法是查找各作用域,首先是local作用域,然后查外围作用域,然后再namespace作用域,最后往global作用域找去,直到找到为止。
    • base class内函数会被derived class相同函数名的覆盖,即使base classes和derived classes内的函数有不同的参数类型也适用,而且无论函数是virtual或non-virtual一体适用。
  3. 条款34:区分接口继承和实现继承

    • 请记住:
      • 接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口
      • pure virtual函数只具体指定接口继承
      • 简朴的(非纯)impure virtual函数具体指定接口继承及缺省实现继承
      • non-virtual函数具体指定接口继承以及强制性实现继承。
    • 成员函数的接口总是会被继承
    • pure virtual函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。因此,声明一个pure virtual函数的目的是为了让derived class只继承函数接口。
    • 声明简朴的(非纯)impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。derived classes继承函数接口,但impure virtual函数会提供一份实现代码,derived classes可能覆写它。允许impure virtual函数同时指定函数声明和函数缺省行为,却有可能形成危险。提供缺省实现给derived classes,但除非它们明白要求否则免谈。解决办法是切断virtual函数接口和其缺省实现之间的连接。
    • 声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现。non-virtual函数代表的意义是不变性凌驾特异性。
    • 一个常见错误是将所有函数声明为non-virtual。non-virtual析构函数尤其会带来问题。另一个常见错误是将所有成员函数声明为virtual。
  4. 条款35:考虑virtual函数以外的其他选择

    • 请记住:
      • virtual函数的替代方法包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一种特殊形式的Template Method
      • 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员
      • tr1::funciton对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。
    • 当你为解决问题而寻找某个设计方法时,不妨考虑virtual函数的替代方案。
      • 使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式。它以public non-virtual成员函数包裹较低访问性的virtual函数
      • 将virtual函数替换为“函数指针成员变量”,这是Strategy设计模式的一种分解表现形式。
      • 以tr1::function成员变量替换virtual函数。
      • 将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。
  5. 条款36:绝不重新定义继承而来的non-virtual函数

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

    • 请记住:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定
    • 静态绑定由又名前期绑定,动态绑定又名后期绑定。对象的所谓静态类型,就是它在程序中被声明时所采用的类型。对象的所谓动态类型则是指“目前所指对象的类型”。
    • “调用一个定义于derived class内的virtual函数”的同时,却使用base class为它所指定的缺省函数值。C++这样做的原因在于运行期效率。
  7. 条款38:通过复合塑模出has或“根据某物实现出”

    • 请记住:
      • 复合的以为和public继承完全不同
      • 在应用域,复合意味has-a。在实现域,复合意味is-implemented-in-terms-of(根据某物实现出)
  8. 条款39:明智而审慎地使用private继承

    • 请记住:
      • Private函数意味is-implemented-in-terms-of(根据某物实现出)。它通常比复合的级别比。但是当derived class需要protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的
      • 和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
    • 如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为base class对象。
    • Private继承意味着is-implemented-in-terms-of。Private继承纯粹只是一种实现技术。private继承意味着只有实现部分被继承,接口部分应略去。
    • C++裁定凡是独立(非附属)对象都必要有非零大小。
      class Empty{};sizeof(Empty)=1;因为面对“大小为零之独立(非附属)对象,通常C++官方勒令默默安插一个char到空对象内。”
    • 这个约束不适用于derived class对象内的base class,因为它们并非独立(非附属)。如果你继承Empty,而不是内含一个那种类型的对象:class HoldAnInt:private Empty { private:int x;};因此sizeof(HoldAnInt) = sizeof(int)。这是所谓的EBO(empty base optimization;空白基类最优化)。EBO一般只在单一继承(而非多重继承)下才可行,统治C++对象布局的那些规则通常表示EBO无法被施行于“拥有多个base”的derived class身上。
    • 现在中的“empty class”并不是真的empty。虽然它们从未拥有non-static成员变量,却往往内含typedefs, enums, static成员变量,或non-virtual函数。STL就有许多技术用途的empty class,其中内含有用的成员,包括base classes unary_function和binary_function。
    • 复合和private继承都意味着is-implemented-in-terms-of,但复合比较容易理解,所以无论什么时候,只要可以,你应该选择复合。但是当derived class需要protected base class的成员,或需要重新定义继承而来的virtual函数时,可以选择private
  9. 条款40:明智而审慎地使用多重继承

    • 请记住:
      • 多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需要
      • virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtualbase class不带任何数据,将是最具实用价值的情况
      • 多重继承的确有正当用途。其中一个情节设计“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。
    • 与C++用来解析重载函数调用的规则相符:在看到是否有个函数可取用之前,C++首先确认这个函数对此调用之言是最佳匹配。找出最佳匹配函数后才可检验器可取用性。
    • 为避免继承得来的成员变量重复,编译器必须提供若干幕后戏法,而其后果是,使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes的成员变量速度慢。
    • virtual base的初始化责任是由继承体系中的最底层负责。
    • 对virtual base classes忠告:第一,非必要不适用virtual class。第二,如果你必须使用virtual base claaes,尽可能避免在其中放置数据。

猜你喜欢

转载自blog.csdn.net/u010991048/article/details/38277629