读书笔记_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成员。

posted @ 2014-03-12 00:09  Jerry19880126  阅读(1446)  评论(0编辑  收藏  举报