写一个视频游戏软件,涉及到宇宙飞船、太空站、小行星等天体:
cpp
class GameObject{...};
class SpaceShip: public GameObject{...};
class SpaceStation: public GameObject{...};
class Asteroid: public GameObject{...};
//检测并处理对象碰撞:
void checkForCollision(GameObject& object1,GameObject& object2)
{
if(theyJustCollided(object1, object2))
processCollision(object1, object2);
else
...
}
碰撞无法同时依据object1和object2两者的动态类型决定;你需要的是某种函数,其行为视一个以上的对象的动态类型决定,C++并未提供这样的函数,并不直接支持"double dispatching"/"multiple dispatch"。
虚函数 + RTTI(运行时期类型辨别)
cpp
//声明一个collide,并在子类中重载:
class GameObject
{
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
class SpaceShip: public GameObject
{
public:
virtual void collide(GameObject& otherObject);
...
};
最一般化的double dispatching实现法:利用if-then-else
来仿真虚函数:
cpp
class CollisionWithUnkownObject{
public:
CollisionWithUnkownObject(GameObject& whatWeHit);
...
};
void SpaceShip::collide(GameObject& otherObject)
{
const type_info& = 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<SpaceShip&> (otherObject);
process a SpaceShip-SpaceStation collision;
}
else if(objectType == typeid(Asteroid)))
{
Asteroid& ss = static_cast<SpaceShip&> (otherObject);
process a SpaceShip-Asteroid collision;
}
else
{
throw CollisionWithUnkownObject(otherObject);
}
}
缺点:造成程序难以维护。
只使用虚函数
使用虚函数实现double dispatching:
将double-dispatching以两个single dispatching(也就是两个分离的虚函数调用)实现出来:其一用来决定第一对象的动态类型,其二用来决定第二对象的动态类型。和先前一样,第一个虚函数调用动作针对的是"接获GameObjects&参数"的collide函数:
cpp
class SpaceShip;
class SpaceStation;
class Asteroid;
class GameObject
{
public:
virtual void collide(GameObject& otherObject) = 0;
virtual void collide(SpaceShip& otherObject) = 0;
virtual void collide(SpaceStation& otherObject) = 0;
virtual void collide(Asteroid& otherObject) = 0;
...
};
class SpaceShip: public GameObject
{
public:
virtual void collide(GameObject& otherObject);
virtual void collide(SpaceShip& otherObject);
virtual void collide(SpaceStation& otherObject);
virtual void collide(Asteroid& otherObject);
...
};
void SpaceShip::collide(GameObject& otherObject)
{
otherObject.collide(*this);
}
//上述实现调用的是:
void SpaceShip::collide(SpaceShip& otherObject)
{
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::collide(SpaceStation& otherObject)
{
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::collide(Asteroid& otherObject)
{
process a SpaceShip-Asteroid collision;
}
缺点:一旦有新的class加入,程序代码必须从头到尾修改。
自行仿真虚函数表格(virtual Function Tables)
回顾:
【C++ Efficiency】理解虚函数、多重继承、虚基类和RTTI
编译器通常通过函数指针数组 (vtbl)来实现虚函数,当某个函数被调用时,编译器便索引至该数组内,取得一个函数指针。有了vtbl,编译器便可以不必执行一大串if-then-else运算,并得以在所有的虚函数调用端产生相同代码,用以:
- 决定正确的vtbl索引;
- 调用vtbl中的索引位置内所指的函数。
cpp
void SpaceShip::hitSpaceShip(SpaceShip& otherObject)
{
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::hitSpaceStation(SpaceStation& otherObject)
{
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::hitAsteroid(Asteroid& otherObject);
{
process a SpaceShip-Asteroid collision;
}
cpp
//中介函数lookup
//接获一个GameObject并返回适当的成员函数指针
class SpaceShip: public GameObject
{
private:
typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);
static HitFunctionPtr lookup(const GameObject& whatWeHit);
...
};
void SpaceShip::collide(GameObject& otherObject)
{
HitFunctionPtr hfp = lookup(otherObject);
if(hfp)
(this->*hfp)(otherObject);
else
throw CollisionWithUnknowObject(otherObject);
}
//lookup的实现
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap;
HitMap::iterator mapEntry = collisionMap.find(typeid(whatWeHit).name());
if(mapEntry == collisionMap.end()) return 0;
return (*mapEntry).second;
}
将自行仿真的虚函数表格(Virtual Function Tables)初始化
cpp
//一个不正确的collisionMap初始化做法
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap;
collisionMap["SpaceShip"] = &hitSpaceShip;
collisionMap["SpaceStation"] = &hitSpaceStation;
collisionMap["Asteroid"] = &hitAsteroid;
...
}
//将"member function指针"放进collisionMap内一次就好
//在collisionMap诞生时刻:
class SpaceShip: public GameObject
{
private:
static HitMap* initializeCollisionMap();
...
};
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static auto_ptr<HitMap> collisionMap(initializeCollisionMap());
//使用智能指针来代替initializeCollisionMap返回的值,避免资源泄露问题:
...
}
cpp
//函数指针的参数均设为GameObject:
class GameObject
{
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
class SpaceShip:public GameObject
{
public:
virtual void collide(GameObject& otherObject);
virtual void hitSpaceShip(GameObject& spaceShip);
virtual void hitSpaceStation(GameObject& spaceShip);
virtual void hitAsteroid(GameObject& spaceShip);
};
//使用dynamic_cast:
void SpaceShip::hitSpaceShip(GameObject& spaceShip)
{
SpaceShip& otherShip = dynamic_cast<SpaceShip&> (spaceShip);
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::hitSpaceStation(GameObject& spaceShip)
{
SpaceStation& otherShip = dynamic_cast<SpaceStation&> (spaceShip);
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::hitAsteroid(GameObject& spaceShip);
{
Asteroid& otherShip = dynamic_cast<Asteroid&> (spaceShip);
process a SpaceShip-Asteroid collision;
}
使用"非成员(non-Member)函数"的碰撞处理函数
cpp
#include "SpaceShip.h"
#include "SpaceStation.h"
#include "Asteroid.h"
namespace
{
//主要的碰撞处理函数
void shipAsteroid(GameObject& spaceShip, GameObject& asteroid);
void shipStation(GameObject& spaceShip, GameObject& spaceStaion);
void asteroidStation(GameObject& asteroid, GameObject& spaceStaion);
...
//次要的碰撞处理函数,只是为了实现对称性:
//对调参数位置,然后调用主要的碰撞处理函数
void asteroidShip(GameObject& asteroid, GameObject& spaceShip)
{
shipAsteroid(spaceShip,asteroid);
}
void stationShip(GameObject& spaceStaion, GameObject& spaceShip)
{
shipStation(spaceShip,spaceStaion);
}
void stationAsteroid(GameObject& spaceStaion, GameObject& asteroid)
{
asteroidStation(asteroid,spaceStaion);
}
...
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);
}
void processCollision(GameObject& object1, GameObject& object2)
{
HitFunctionPtr phf = lookup(typeid(object1).name(),typeid(object2).name());
if(phf) phf(object1,object2);
else throw UnknowCollision(object1,object2);
}
继承+自行仿真的虚函数表格
扩大继承体系时,必须要求每个人都重新编译。
将自行仿真的虚函数表格初始化(再度讨论)
cpp
//利用一个map来存储撞击处理函数
//放进某个class内,该class提供一些成员函数,动态修改map的内容:
class CollisionMap
{
public:
typedef void (*HitFunctionPtr)(GameObject&,GameObject&);
void addEntry(const string& type1,
const string& type2,
HitFunctionPtr collisionFunction,
bool symmtric = true);
void remove(const string& type1,const string& type2);
HitFunctionPtr lookup(const string& type1,const string& type2);
static CollisionMap& theCollisionMap();
private:
CollisionMap();
CollisionMap(const CollisionMap&);
};
void shipAsteroid(GameObject& spaceShip,GameObject& asteroid);
CollisionMap::theCollisionMap().addEntry("SpaceShip","Asteroid",
&shipAsteroid);
void shipStation(GameObject& spaceShip,GameObject& spaceStaion);
CollisionMap::theCollisionMap().addEntry("SpaceShip","spaceStaion",
&shipStation);
void asteroidStation(GameObject& asteroid,GameObject& spaceStaion);
CollisionMap::theCollisionMap().addEntry("asteroid","spaceStaion",
&asteroidStation);
//确保map在对应的任何碰撞发生之前就加入map之中
//办法之一是令GameObject's subclasses的constructors加以检查
class RegisterCollisionFunction
{
public:
RegisterCollisionFunction(
const string& type1,
const string& type2,
CollisionMap::HitFunctionPtr collisionFunction,
bool symmetric = true)
{
CollisionMap.theCollisionMap.addEntry(type1,type2,
collisionFunction,
symmetric);
}
};
//client于是可以利用这种类型的全局对象来自动注册它们所需的函数:
RegisterCollisionFunction cf1("SpaceShip","Asteroid",&shipAsteroid);
RegisterCollisionFunction cf2("SpaceShip","SpaceStation",&shipStation);
RegisterCollisionFunction cf3("Asteroid","SpaceStation",&asteroidStation);
...
int main( int argc, char *argv[])
{
...
}
//如果加入一个新的派生类:
class Satellite: public GameObject{...};
//并写出一个或多个新的碰撞处理函数:
void satelliteShip(GameObject& satellite,GameObject& spaceShip);
void satelliteAsteroid(GameObject& satellite,GameObject& asteroid);
//这些新函数可以类似方法加入到map之中,不需要扰动原有的代码:
RegisterCollisionFunction("Satellite","SpaceShip",&satelliteShip);
RegisterCollisionFunction("Satellite","Asteroid",&satelliteAsteroid);
还是没有完美的办法可以实现double dispatch,但此方法可以轻视完成一个以map为基础的实现品。