举书上的例子,考虑一个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成员。