条款M31:让函数根据一个以上的对象来决定怎么虚拟:(2)通过模拟虚函数表实现二重调度

通过模拟虚函数表实现二重调度相比条款M31(1)中所提方法效率更高,但更复杂。(PS:但书中此部分代码语法很漂亮,经典)
虚函数表:根据对象动态类型选择调用某一个函数。

1、整体代码:
a) 基础继承体系代码:

class GameObject {  //抽象基类
public: 
  virtual void collide(GameObject& otherObject) = 0; 
  ... 
}; 
class SpaceShip: public GameObject {  // 其中一个派生类,其他类似
public: 
  virtual void collide(GameObject& otherObject);  // 处理碰撞
  virtual void hitSpaceShip(GameObject& otherObject); 
  virtual void hitSpaceStation(GameObject& otherObject); 
  virtual void hitAsteroid(GameObject& otherobject); 
  ... 
};
  • 与上一篇文章中RTTI类似,GameObjcet 类只有一个处理碰撞的函数,它实现必须的二重调度的第一重
  • SpaceShip::collide 中,需要一个方法来映射传入参数 otherObject 的动态类型到一个成员函数指针(指向一个适当的碰撞处理函数hit...),这个方法就是模拟虚函数表

b) 模拟虚函数表方法的调用过程:
lookup 函数接受一个 GameObject参数,返回相应的成员函数指针。

//.h
class SpaceShip: public GameObject { 
private: 
  typedef void (SpaceShip::*HitFunctionPtr)(GameObject&); //函数指针类型定义
  static HitFunctionPtr lookup(const GameObject& whatWeHit);  //lookup函数声明
  ... 
};

//.cpp
void SpaceShip::collide(GameObject& otherObject) 
{
  HitFunctionPtr hfp = lookup(otherObject); // 返回与参数类型对应的成员函数指针
  if (hfp) {                            // 若找到则调用
    (this->*hfp)(otherObject);         // 注意调用代码
  } 
  else {                                // 若没找到,抛出异常
    throw CollisionWithUnknownObject(otherObject); 
  } 
}

因此,此时问题的关键就是写出模拟虚函数表的方法

c) 模拟虚函数表的方法实现:
标准模板库提供的 map 模板来实现映射表

//.h
class SpaceShip: public GameObject { 
private: 
  typedef void (SpaceShip::*HitFunctionPtr)(GameObject&); 
  typedef map<string, HitFunctionPtr> HitMap; // 参数类型名与成员函数指针的映射表
  //此处运用了类型重定义
  ... 
}; 

//.cpp
SpaceShip::HitMap* SpaceShip::initializeCollisionMap() // 初始化映射表函数,返回映射表对象指针
{ 
  HitMap *phm = new HitMap;  //定义映射表
  (*phm)["Space Ship"] = &hitSpaceShip;  //赋值映射表,注意map对象指针的用法
  (*phm)["Space Station"] = &hitSpaceStation; 
  (*phm)["Asteroid"] = &hitAsteroid; 
  return phm; 
}

SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit) 
//返回类型为SpaceShip::HitFunctionPtr,即成员函数指针
{ 
  // 定义一个映射表并初始化,智能指针管理映射表对象
  static auto_ptr<HitMap> collisionMap(initializeCollisionMap());
  //此处解释见“注1”
  HitMap::iterator mapEntry= collisionMap->find(typeid(whatWeHit).name());
  if (mapEntry == collisionMap.end()) return 0; //不存在返回空指针
  return (*mapEntry).second; //否则返回找到的成员函数指针
  也可以写成 return mapEntry->second; 
  ... 
}

注1:typeid()的返回结果上总可以调用的(可移植的)一个成员函数是name()(它返回对象的动态类型的名字)
注:此处用成员函数实现两对象的处理函数,可以改为非成员函数处理,这样还省去了两对象先后顺序不同带来的调用函数不同问题(A与B碰撞,B与A碰撞)。

总结:

1、利用STL中map模拟虚函数表,映射传入参数 otherObject 的动态类型到一个成员函数指针
2、typeid()的返回结果上总可以调用的(可移植的)一个成员函数是name()(它返回对象的动态类型的名字)
3、STL函数库中find()返回的是迭代器类型。
4、make_pair 是标准运行库中的一个转换函数(模板),使用他可以避免了在构造 pair 对象时需要申明类型的麻烦。如做函数参数时直接传入make_pair(class1, class2); 代替pair<string,string>(class1, class2);
5、typedef map< pair<string,string>, int > map1; //结合pair的map

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值