二重调度(二):用虚函数加RTTI 实现

本文探讨了C++中如何使用虚函数实现单一调度,并通过运行时类型识别(RTTI)实现双重调度的方法。详细介绍了如何在基类GameObject中声明虚函数collide,并在派生类如SpaceShip中重载此函数。此外,还讨论了使用RTTI进行类型检查的利弊,以及在遇到未知类型时如何处理异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虚函数实现了一个单一调度,这只是我们所需要的一半;编译器为我们实现虚函数,

所以我们在GameObject中申明一个虚函数collide。这个函数被派生类以通常的形式重载:  


class GameObject { 
public: 
  virtual void collide(GameObject& otherObject) = 0; 
  ... 

}; 


class SpaceShip: public GameObject { 
public: 
  virtual void collide(GameObject& otherObject); 
  ... 

}; 


我在这里只写了派生类SpaceShip的情况, SpaceStation和Asteroid的形式完全一样

的。 


实现二重调度的最常见方法就是和虚函数体系格格不入的if...then...else链。在这

种刺眼的体系下,我们首先是发现otherObject的真实类型,然后测试所有的可能:

 

// if we collide with an object of unknown type, we 
// throw an exception of this type: 
class CollisionWithUnknownObject { 
public: 
  CollisionWithUnknownObject(GameObject& whatWeHit); 
  ... 

}; 


void SpaceShip::collide(GameObject& otherObject) { 
  const type_info& objectType = typeid(otherObject); 
  if (objectType == typeid(SpaceShip)) { 
    SpaceShip& ss = static_cast<SpaceShip&>(otherObject); 
    process a SpaceShip-SpaceShip collision; 
  } 
  else if (objectType == typeid(SpaceStation)) { 
    SpaceStation& ss = 
      static_cast<SpaceStation&>(otherObject); 
    process a SpaceShip-SpaceStation collision; 
  } 
  else if (objectType == typeid(Asteroid)) { 
    Asteroid& a = static_cast<Asteroid&>(otherObject); 
    process a SpaceShip-Asteroid collision; 
  } 
  else { 
    throw CollisionWithUnknownObject(otherObject); 
  } 


注意,我们需要检测的只是一个对象的类型。另一个是*this,它的类型由虚函数体系
判断。我们现在处于SpaceShip的成员函数中,所以*this肯定是一个SpaceShip对象,因此我们只需找出otherObject的类型。 这儿的代码一点都不复杂。它很容易实现。也很容易让它工作。RTTI只有一点令人不安:它只是看起来无害。实际的危险来自于最后一个else语句,在这儿抛了一个异常。 我们的代价是几乎放弃了封装,因为每个collide函数都必须知道所以其它同胞类中的版本。尤其是,如果增加一个新的类时,我们必须更新每一个基于RTTI的if...then...else链以处理这个新的类型。即使只是忘了一处,程序都将有一个bug,而且它还不显眼。 编译器也没办法帮助我们检查这种疏忽, 因为它们根本不知道我们在做什么 , 这种类型相关的程序在C语言中已经很有一段历史了,而我们也知道,这样的程序本质上是没有可维护性的。扩充这样的程序最终是不可想象的。这是引入虚函数的主意原因:将产生和维护类型相关的函数调用的担子由程序员转给编译器。当我们用RTTI实现二重调度时,我们正退回到过去的苦日子中。 


这种过时的技巧在C语言中导致了错误,它们C++语言也仍然导致错误。认识到我们自己的脆弱,我们在collide函数中加上了最后的那个else语句,以处理如果遇到一个未知

类型。这种情况原则上说是不可能发生的,但在我们决定使用RTTI时又怎么知道呢?有很多种方法来处理这种未曾预料的相互作用,但没有一个令人非常满意。在这个例子里,我们选择了抛出一个异常,但无法想象调用者对这个错误的处理能够比我们好多少,因为我们遇到了一个我们不知道其存在的东西。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值