通过模拟虚函数表实现二重调度相比条款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