Effective cpp 读书笔记7

来源:互联网 发布:对比两组数据的差异性 编辑:程序博客网 时间:2024/05/16 15:04

继承与面向对象设计

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

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

    • 这一条意思是,注意你在base class定义的函数,因为公共继承是is-a的关系,但是这种关系是具体问题具体分析的,不要用常识去理解

33.避免遮掩继承而来的名称

  1. derived classes内的名称会遮掩base classes的名称。在public继承下从来没有人希望如此

  2. 为了让被遮掩的名称重见天日,可使用using声明式或转交函数

    • 继承体系内存在函数遮掩。这种遮掩很奇怪,只要函数名重复,不看形参是否一致,就必然会存在遮掩。(派生类的f(double)会遮掩基类的f())
    • 编译器名称查询:首先看作用域内是否有对应函数,其次看类覆盖的作用域有没有,最后看继承的上级有没有
    • 两种方法让被遮掩的函数重见天日:在对应的derived class的域内(public域)用using声明;在派生类的其他函数中,显式调用基类被遮掩的函数

34.区分接口继承和实现继承

  1. 接口继承和实现继承不同。在public继承下,derived classes总是继承base class的接口

  2. pure virtual函数只具体指定接口继承

  3. 简朴的impure virtual函数具体指定接口继承和缺省实现继承

  4. non-virtual函数具体指定接口继承以及强制性实现继承

    • pure virtual函数:只提供接口
    • impure virtual函数:提供接口和缺省实现。这里,接口和缺省实现应该分开。这里有2种方法
      – 将这种函数变成pure virtual的形式,同时提供另一个函数作为缺省实现,在派生类中,暴露这个缺省实现(protected)
      – 将它声明为pure virtual并提供实现,在派生类的对应函数中调用基类的缺省实现
    • non-virtual是为了另derived classes继承函数的接口和一份强制实现。它代表的意义是不变形凌驾于特异性
    • 程序员2大错误:所有函数声明为non-virtual(使得derived classes没有空余空间进行特化);所有函数声明为virtual(立场不坚定)
  5. virtual函数的成本:80-20法则(程序80%的执行时间花在20%的代码上),意味着你的函数调用有80%是virtual,而不冲击程雪的效率。当你担心virtual的成本,请先将精力放在20%的代码上


35.考虑virtual函数以外的其他选择(策略模式)

  1. virtual函数的替代方案包括NVI手法以及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式(模板设计模式就是指普通的模板基类)

  2. 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员

  3. tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定的目标签名式兼容”的所有可调用物

    • non-Virtual Interface替换virtual:基类的virtual函数设定为private,设计public的函数定义死上下文,并在其中调用virtual(外覆盖器),这样,派生类只需要复写virtual函数,上下文关系都被基类定死
    • Function Pointer替换virtual:函数指针将动态变换的函数部分从class中移出。但是,任何时候,将class内的技能替换为class外部的等价机能都会存在争议
    • tr1::function替换virtual:它能覆盖的形式更广,比函数指针要好
    • 用传统的Strategy模式替代:主要是将本继承体系的virtual定义好,用另一个继承体系来设定其他函数去调用这个virtual,可能两个继承体系之间存在复用

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

  1. 这一条主要注意动态和静态绑定,virtual肯定是动态绑定。而我们定义指针的时候,是静态绑定的关系。所以下面的例子,pB静态绑定为B*,由于调用函数没有virtual,则自动调用了继承的B的对应函数。Reference也会有这种行为
class B {    public: void mf();}class D: public B {    public: void mf()}D x;B* pB = &x;D* pD = &x;pB->mf();   // 调用B的mf函数pD->mf();   // 调用D的mf函数

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

  1. 因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西却是动态绑定

    • 静态类型:在程序中被声明时所采用的类型
    • 动态类型:目前所指对象的类型
  2. 小结:如果基类的virtual函数有缺省值,因为是静态绑定,当你用指针动态绑定对象的时候,尽管能调用对应类型的virtual函数,但这个函数的缺省参数仅与指针的声明对象的virtual函数有关,与动态绑定的对象无关


39.明智而审慎地使用private继承

  1. private继承意味着is-implemented-in-terms-of。它通常比复合的级别低。但是当derived class需要访问protected base class的成员,或者需要重新定义继承而来的virtual,这么设计是合理的

  2. 和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺度最小化”的程序库开发者是重要的

    • 继承关系是private的时候,不在是is-a的关系,编译器不会自动完成类型转化(这意味着动态绑定没有意义)
    • private继承:只有实现部分被继承,接口部分略去。如果D以private形式继承B,则D对象是根据B对象而得,没有其他任何意义了
    • private和复合:两者其实表达的意义一样。尽量用复合,必要时候采用private。必要时候是指:当protected成员需要被调用或virtual函数需要被覆写或者极端情况
    • 极端情况:为了达到空间最优化。因为绝大多数编译器对空类(empty class)都会默认安插一个char,使得空类大小为sizeof为1,如果有类复用了它,可能因为内存的齐位需要,增大空间。而如果私有继承,就不会安插char,这就是空白基类最优化(EBO)
    • 如何阻止derived class重新定义virtual函数:放弃基类的继承,在原本的derived class内定义新的类继承基类,那么,后续的派生类继承原本的这个derived class的时候,无法暴露virtual函数让它们覆写

40.明智而审慎地使用多重继承

  1. 多重继承比单一继承复杂。它可能导致新的歧义,以及对virtual继承的需要

  2. virtual继承会增加空间、时间、初始化的复杂度。如果virtual base classes不带任何数据,将是最具使用价值的情况

    • 问题:当MI进入设计,程序可能从一个以上的base class继承相同的名称;当存在砖石型多重继承,C++缺省做法是每个路径执行复制,存在重复
    • virtual继承:
      – 当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次
      – C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类对虚基类的构造函数的调用
    • virtual继承用法:
      – 应当在该基类的所有直接派生类中将基类声明为虚基类
      – 在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化
      – 非必要不要使用virtual base;如果必须使用,尽可能避免在其中放数据
0 0
原创粉丝点击