二重调度(五):使用非成员的碰撞处理函数实现

我们现在知道了怎么构造一个类似vtbl的映射表以实现二重调度的第二部分,并且我

们也知道了怎么将映射表的实现细节封装在lookup函数中。因为这张表包含的是指向成员函数的指针,所以在增加新的GameObject类型时仍然需要修改类的定义,这还是意味着所有人都必须重新编译, 即使他们根本不关心这个新的类型。 例如, 如果增加了一个Satellite类型, 我们不得不在SpaceShip类中增加一个处理SpaceShip和Satellite对象间碰撞的函数。所有SpaceShip的用户不得不重新编译,即使他们根本不在乎Satellite对象的存在。这个问题将导致我们否决只使用虚函数来实现二重调度,解决方法是只需做小小的修改。 


如果映射表中包含的指针指向非成员函数,那么就没有重编译问题了。而且,转到非

成员的碰撞处理函数将让我们发现一个一直被忽略的设计上的问题,就是,应该在哪个类里处理不同类型的对象间的碰撞?在前面的设计中,如果对象1和对象2碰撞,而正巧对象1是processCollision的左边的参数,碰撞将在对象1的类中处理;如果对象2正巧是左边的参数,碰撞就在对象2的类中处理。这个有特别的含义吗?是不是这样更好些:类型A和类型B的对象间的碰撞应该既不在A中也不在B中处理, 而在两者之外的某个中立的地方处理? 


如果将碰撞处理函数从类里移出来,我们在给用户提供类定义的头文件时,不用带上

任何碰撞处理函数。我们可以将实现碰撞处理函数的文件组织成这样: 

#include "SpaceShip.h" 
#include "SpaceStation.h" 
#include "Asteroid.h" 


namespace {                     // unnamed namespace — see below 
  // primary collision-processing functions 
  void shipAsteroid(GameObject& spaceShip, 
                    GameObject& asteroid); 


  void shipStation(GameObject& spaceShip, 
                   GameObject& spaceStation); 


  void asteroidStation(GameObject& asteroid, 
                       GameObject& spaceStation); 
  ... 


  // secondary collision-processing functions that just 
  // implement symmetry: swap the parameters and call a 
  // primary function 


  void asteroidShip(GameObject& asteroid, 
                    GameObject& spaceShip) {

    shipAsteroid(spaceShip, asteroid); 

  } 


   void stationShip(GameObject& spaceStation, 
                   GameObject& spaceShip) { 

    shipStation(spaceShip, spaceStation);

  } 


   void stationAsteroid(GameObject& spaceStation, 
                       GameObject& asteroid) { 

     asteroidStation(asteroid, spaceStation);

   } 
  ... 


  // see below for a description of these types/functions 
  typedef void (*HitFunctionPtr)(GameObject&, GameObject&); 


  typedef map< pair<string,string>, HitFunctionPtr > HitMap; 


  pair<string,string> makeStringPair(const char *s1, 
                                     const char *s2); 

  

  HitMap * initializeCollisionMap(); 


  HitFunctionPtr lookup(const string& class1, 
                        const string& class2); 
} // end namespace 


void processCollision(GameObject& object1, 
                        GameObject& object2) 

  HitFunctionPtr phf = lookup(typeid(object1).name(), 
                              typeid(object2).name()); 
  if (phf) 

      phf(object1, object2); 
  else 

      throw UnknownCollision(object1, object2); 


注意,用了无名的命名空间来包含实现碰撞处理函数所需要的函数。无名命名空间中
的东西是当前编译单元(其实就是当前文件)私有的--很象被申明为文件范围内static
的函数一样。有了命名空间后,文件范围内的static已经不赞成使用了,你应该尽快让自
己习惯使用无名的命名空间(只要编译器支持) 。 


       理论上,这个实现和使用成员函数的版本是相同的,只有几个轻微区别。第一,
HitFunctionPtr现在是一个指向非成员函数的指针类型的重定义。第二,意料之外的类
CollisionWithUnknownObject被改叫UnknownCollision,第三,其构造函数需要两个对象作参数而不再是一个了。这也意味着我们的映射需要三个消息了:两个类型名,一个
HitFunctionPtr。 


// we use this function to create pair<string,string> 
// objects from two char* literals. It's used in 
// initializeCollisionMap below. Note how this function 
// enables the return value optimization


namespace {          // unnamed namespace again — see below 

  pair<string,string> makeStringPair(const char *s1, const char *s2) { 

  return pair<string,string>(s1, s2);   

  } 

} // end namespace 


namespace {          // still the unnamed namespace — see below   HitMap *        initializeCollisionMap() 
  { 
    HitMap *phm = new HitMap; 
    (*phm)[makeStringPair("SpaceShip","Asteroid")] = &shipAsteroid; 
    (*phm)[makeStringPair("SpaceShip", "SpaceStation")] = &shipStation; 
    ... 
    return phm; 
  } 
} // end namespace 


lookup函数也必须被修改以处理pair<string,string>对象,并将它作为映射表的第
一部分: 
namespace {          // I explain this below — trust me 
  HitFunctionPtr lookup(const string& class1, const string& class2) 
  { 
    static auto_ptr<HitMap> collisionMap(initializeCollisionMap()); 


    // see below for a description of make_pair 
    HitMap::iterator mapEntry= collisionMap->find(make_pair(class1, class2)); 

    if (mapEntry == collisionMap->end()) 

       return 0; 
    return (*mapEntry).second; 
  } 
} // end namespace 


因为makeStringPair、initializeCollisionMap和lookup都是申明在无名的命名空
间中的,它们的实现也必须在同一命名空间中。这就是为什么这些函数的实现在上面被写在了一个无名命名空间中的原因(必须和它们的申明在同一编译单元中) :这样链接器才能正确地将它们的定义(或说实现)与它们的前置申明关联起来。 


我们最终达到了我们的目的。如果增加了新的GaemObject的子类,现存类不需要重新
编译(除非它们用到了新类) 。没有了RTTI的混乱和if...then...else的不可维护。增加
一个新类只需要做明确定义了的局部修改:在initializeCollisionMap中增加一个或多个
新的映射关系, 在processCollision所在的无名的命名空间中申明一个新的碰撞处理函数。


我们花了很大的力气才走到这一步,但至少努力是值得的。是吗?是吗? 也许吧。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值