zoukankan      html  css  js  c++  java
  • 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择

    举书上的例子,考虑一个virtual函数的应用实例:

     1 class GameCharacter
     2 {
     3 private:
     4     int BaseHealth;
     5 public:
     6     virtual int GetHealthValue() const  // 返回游戏人物的血量
     7     {
     8         return BaseHealth;
     9     }
    10 
    11     int GetBaseHealth() const
    12     {
    13         return BaseHealth;
    14     }
    15 };
    16 
    17 class KnightBoss : public GameCharacter
    18 {
    19 public:
    20     virtual int GetHealthValue() const
    21     {
    22         return GetBaseHealth() * 2;
    23     }
    24 };
    25 
    26 int main()
    27 {
    28     GameCharacter *CommonCharacter = new GameCharacter(100);
    29     GameCharacter *Boss = new KnightBoss(100);
    30     cout << "CommonCharacter heath = " << CommonCharacter->GetHealthValue() << endl; //返回100
    31     cout << "KnightBoss heath = " << Boss->GetHealthValue() << endl; // 返回200
    32 }

    GetHealthValue会根据不同类型的游戏角色来获得相应的血量。但这里将虚函数是public的,NVI(Non-Virutal Interface)的一个流派主张所有的虚函数都是private的,将父类与子类都会使用的前置方法与后置方法单独作一个non-virtual的函数,像下面这样:

     1 class GameCharacter
     2 {
     3 private:
     4     int BaseHealth;
     5 public:
     6     GameCharacter(int bh = 100) :BaseHealth(bh){}
     7 
     8     int GetBaseHealth() const
     9     {
    10         return BaseHealth;
    11     }
    12 
    13     int GetHealthValue()
    14     {
    15         cout << "可以是一些前置检查" << endl;
    16         int Result = DoGetHealthValue();
    17         cout << "可以是一些后置处理" << endl;
    18         return Result;
    19     }
    20 
    21 private:
    22     virtual int DoGetHealthValue() const  // 返回游戏人物的血量
    23     {
    24         return BaseHealth;
    25     }
    26 };
    27 
    28 class KnightBoss : public GameCharacter
    29 {
    30 public:
    31     KnightBoss(int bh) :GameCharacter(bh){}
    32 
    33 private:
    34     virtual int DoGetHealthValue() const
    35     {
    36         return GetBaseHealth() * 2;
    37     }
    38 };

    main函数的接口不必有任何的变化。这里是把父类与子类有特异性的方法都写在了各自private范围内。这样的好处,是可以做一些善前善后的事情(如程序中的cout方法所示),善前方法可以是锁定互斥器,打印日志,验证输入数据合法性等,善后的方法可以是解除锁定,验证事后数据合法性等。

    注意这里是将虚函数都置成了private的,但编译器生成的虚表指针则不是private的,否则会因private成员变量根本不被继承而无法实现多态。NVI的方式也不是绝对的,比如虚析构函数,它必须是public的,才能确保它的子类,以及子类的子类们能够顺利释放资源。

    那么还有其他方法来替代virtual函数吗?virtual函数的本质是由虚指针和虚表来控制,虚指针指向虚表中的某个函数入口地址,就实现了多态。因此,我们也可以仿照一个虚指针指向函数的手法,来做一个函数指针。像下面这样:

     1 class GameCharacter
     2 {
     3 private:
     4     int BaseHealth;
     5 public:
     6     typedef int(*HealthCalcFunc)(const GameCharacter&);
     7     GameCharacter(int bh = 100, HealthCalcFunc Func= NULL) 
     8         :BaseHealth(bh), HealthFuncPoniter(Func){}
     9 
    10     int GetHealthValue()
    11     {
    12         if (HealthFuncPoniter)
    13         {
    14             return HealthFuncPoniter(*this);
    15         }
    16         else
    17         {
    18             return 0;
    19         }
    20     }
    21 
    22     int GetBaseHealth() const
    23     {
    24         return BaseHealth;
    25     }
    26 private:
    27     HealthCalcFunc HealthFuncPoniter;
    28 };
    29 
    30 class KnightBoss : public GameCharacter
    31 {
    32 public:
    33     KnightBoss(int bh, HealthCalcFunc Func) :GameCharacter(bh, Func){}
    34 
    35 private:
    36 
    37 };
    38 
    39 int HealthCalcForCommonMonster(const GameCharacter& gc)
    40 {
    41     return gc.GetBaseHealth();
    42 }
    43 
    44 int HealthCalcForKnightBoss(const GameCharacter& gc)
    45 {
    46     return 2 * gc.GetBaseHealth();
    47 }

    main函数的内容仍然不需要改变,得到的结果是相同的,其实这里只是用函数指针模拟了虚表指针而已。在父类中声明了这个函数指针的形式,它返回一个int值,但因为需要用到GameCharacter里面的方法,所以形参是GameCharater的引用。父类的有一个私有的成员变量,它就是可以指向具体函数的函数指针。在父类与子类的构造函数里带入不同的函数,就可以实现调用父类GetHealthValue时产生的不同计算方法。

    上面是用了typedef进行了函数指针类型声明,然后定义了指定形参与返回值的函数,在构造时将类中的函数指针指向特定的函数,那么我们就会想,能不能将用类(而不是typedef)来做呢?请看下面的示例:

     1 class HealthCalcFunctionBaseClass
     2 {
     3 public:
     4     virtual int CalcHealth(int BaseValue) // 书上这里用的是const GameCharacter&,然后前置声明了class GameCharacter,但因为要用到GameCharacter的具体方法,所以编译器会报错,不知道你们是如何解决这个问题的
     5     {
     6         return BaseValue;
     7     }
     8 };
     9 
    10 class HealthCalcFunctionDerivedClass : public HealthCalcFunctionBaseClass
    11 {
    12 public:
    13     virtual int CalcHealth(int BaseValue)
    14     {
    15         return 2 * BaseValue;
    16     }
    17 };
    18 
    19 class GameCharacter
    20 {
    21 private:
    22     int BaseHealth;
    23 public:
    24     GameCharacter(int bh = 100, HealthCalcFunctionBaseClass *Func = NULL)
    25         :BaseHealth(bh), HealthFuncPoniter(Func){}
    26 
    27     int GetHealthValue()
    28     {
    29         if (HealthFuncPoniter)
    30         {
    31             return HealthFuncPoniter->CalcHealth(GetBaseHealth());
    32         }
    33         return 0;
    34     }
    35 
    36     int GetBaseHealth() const
    37     {
    38         return BaseHealth;
    39     }
    40 private:
    41     HealthCalcFunctionBaseClass *HealthFuncPoniter;
    42 };
    43 
    44 class KnightBoss : public GameCharacter
    45 {
    46 public:
    47     KnightBoss(int bh, HealthCalcFunctionBaseClass *Func) :GameCharacter(bh, Func){}
    48 };
    49 
    50 // main函数的接口需要改一下
    51 int main()
    52 {
    53     HealthCalcFunctionBaseClass *CommonMonsterCalc = new HealthCalcFunctionBaseClass();
    54     HealthCalcFunctionBaseClass *BossCalc = new HealthCalcFunctionDerivedClass();
    55     GameCharacter *CommonCharacter = new GameCharacter(100, CommonMonsterCalc);
    56     GameCharacter *Boss = new KnightBoss(100, BossCalc);
    57 
    58     cout << "CommonCharacter heath = " << CommonCharacter->GetHealthValue() << endl; //返回100
    59     cout << "KnightBoss heath = " << Boss->GetHealthValue() << endl; // 返回200
    60 }

    有的读者会说,既然是在讨论如果用其他方法来替换virtual函数,但这里用来替换virtual函数的还是一个带virtual的类啊,的确,这只是作者的应用strategy模式的一种实现方式而已,第一种NVI方法本身也是离不开private的virutal函数,如果你觉得矛盾,那就还是用纯函数指针的方法吧。(作者也许表达的是想用一种特殊的virtual形式,去代替我们通常意义上见到的virtual函数吧)

    书上还说到仿函数的替换方法,有兴趣可以看看。下面总结一下:

    1. virtual函数的替代方案NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式;

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

  • 相关阅读:
    Django【二】自定义web框架
    Django【一】web框架的本质
    Django【零】HTTP协议
    【前端】bootstrap引入
    【前端】jquery基础学习
    socket模块粘包现象理解以及解决思路
    面向对象编程学习笔记
    socket模块
    CPU缓存体系对Go程序的影响
    测试Go代码 单元测试
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/3595411.html
Copyright © 2011-2022 走看看