本部分的代码来自于more effective Item 31,部分代码是我改的(很少)
一开始看见这个主题有点懵,没搞懂意思。看了书里的例子后发现这确实是一个很重要的问题
class GameObject{...};
class SpaceShip:public GameObject{...};
class SpaceStation:public GameObject{...};
class Asteroid:public GameObject{...};
void checkForCollision(GameObject& obj1,GameObject& obj2)
{
if(theyJustCollided(obj1,obj2))
{
processCollision(obj1,obj2);
}
else
{
...
}
}
情景是模拟太空游戏中,不同天体的碰撞,看完这个流程后,发现调用processCollision需要两个object,需要知道这两个的类型,不然不好调用碰撞处理函数。这就需要依据两者的动态类型决定,单一的对象虚化是不够的。
版本一:
class SpaceShip;
class SpaceStation;
class Asteriod;
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 hitAsteriod(GameObject&otherObject);
private:
typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);//指向形参为(GameObject&)的函数指针
typedef map<string, HitFunctionPtr> HitMap;
static HitFunctionPtr lookup(const GameObject& whatWeight);//查找并返回碰撞函数
static HitMap *initializeCollisionMap();
};
void SpaceShip::hitSpaceShip(GameObject&otherObject) {
SpaceShip&otherObject = dynamic_cast<SpaceShip&>(otherObject);//将数据类型转回本身类型
}
void SpaceShip::hitSpaceStation(GameObject&otherObject) {
SpaceStation&otherObject = dynamic_cast<SpaceStation&>(otherObject);
}
void SpaceShip::hitAsteriod(GameObject&otherObject) {
Asteriod&otherObject = dynamic_cast<Asteriod&>(otherObject);
}
void SpaceShip::collide(GameObject &otherObject) {
HitFunctionPtr hfp = lookup(otherObject);
if (hfp)
(this->*hfp)(otherObject);
else
throw (1);
}
SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject&whatWeight) {
static auto_ptr<HitMap> collisionMap(initializeCollisionMap());
HitMap::iterator mapEntry = collisionMap->find(typeid(whatWeight).name());
if (mapEntry == collisionMap->end())return 0;
return (*mapEntry).second;
}
SpaceShip::HitMap *SpaceShip::initializeCollisionMap() {//使用map存储键值对,value对应函数
HitMap*phm = new HitMap;
(*phm)["SpaceShip"] = &hitSpaceShip;
(*phm)["SpaceStation"] = &hitSpaceStation;
(*phm)["Asteriod"] = &hitAsteriod;
return phm;
}
class SpaceStation :public GameObject {
};
class Asteriod :public GameObject {
};
缺点:
这种方法在如果加入新的类型,需要修改class的定义,意味着所有的客户都必须重新编译,甚至不在乎新型对象的人,这个操作和单虚函数的版本和if-else版本没多少差距,甚至不如他俩直接,所以接下来要做的就是将碰撞函数抽离出class让其成为非成员函数。
版本二:
class SpaceShip;
class SpaceStation;
class Asteriod;
class GameObject {
public:
virtual void collide(GameObject &otherObject) = 0;
};
namespace {
class SpaceShip :public GameObject {};
class SpaceStation :public GameObject {};
class Asteriod :public GameObject {};
void shipAsteriod(GameObject &spaceShip, GameObject &asteriod) {//具体碰撞处理函数
}
void shipStation(GameObject&spaceShip, GameObject &spaceStation) {
}
void asteriodStation(GameObject&asteriod, GameObject&spaceStation) {
}
void asteriodShip(GameObject&asteriod, GameObject&spaceShip) {//两种情况结果应该一样
shipAsteriod(spaceShip, asteriod);
}
void stationShip(GameObject&spaceStation, GameObject&spaceShip) {
shipStation(spaceShip, spaceStation);
}
void stationAsteriod(GameObject&spaceStaion, GameObject&asteriod) {
asteriodStation(asteriod, spaceStaion);
}
typedef void(*HitFunctionPtr)(GameObject &, GameObject&);//指向两个形参的指针函数
typedef map<pair<string, string>, HitFunctionPtr> HitMap;
pair<string, string>makeStringPair(const char *s1, const char *s2);//构造pair<string,string>
HitMap*initializeCollisionMap();//HitMap初始化
HitFunctionPtr lookup(const string&class1, const string&class2);//查找对应碰撞处理函数
}
void processCollision(GameObject&o1, GameObject&o2) {//调用碰撞处理函数
HitFunctionPtr phf = lookup(typeid(o1).name(), typeid(o2).name());
if (phf)phf(o1, o2);
else throw(1);
}
namespace {
pair<string, string>makeStringPair(const char *s1, const char *s2) {
return pair<string, string>(s1, s2);
}
}
namespace {
HitMap*initializeCollisionMap() {
HitMap*phm = new HitMap;
(*phm)[makeStringPair("SpaceShip", "Asteriod")] = &shipAsteriod;
(*phm)[makeStringPair("SpaceShip", "SpaceStation")] = &shipStation;
(*phm)[makeStringPair("Asteriod", "SpaceStaion")] = &asteriodStation;
return phm;
}
}
namespace {
HitFunctionPtr lookup(const string &class1, const string &class2) {
static auto_ptr<HitMap>collisionMap(initializeCollisionMap());
HitMap::iterator mapEntry = collisionMap->find(make_pair(class1, class2));
if (mapEntry == collisionMap->end())return 0;
return (*mapEntry).second;
}
}
这个版本使用了匿名namespace,对于namespace内的每样东西对其所驻在的编译单元而言都是私有的。其效果就好像在文件里将函数声明为static一样。
缺点:
如果现在有一个Military Ship继承自SpaceShip,如果其与Asteriod相撞,我们期望调用
void shipAsteriod(GameObject&spaceShip,GameObject&asteroid);
但事实并未如此,会进行报错,因为这个函数针对的是SpaceShip和Asteriod,虽然是继承关系,但map里没有这条目录。
另一个缺点是无法对map进行新增,移除和修改等操作。一旦登陆了一个用来处理碰撞的函数,就永远停在那里了。
版本三:
class CollisionMap {
public:
typedef void(*HitFunctionPtr)(GameObject &, GameObject&);
void addEntry(const string &type1, const string &type2, HitFunctionPtr collisionFunction, bool symmetric = true){
//直接对lookup函数进行操作,将这条目录加入到map中。
}
void removeEntry(const string &type1, const string&type2){
//将这条pair信息从map中移除,操作:查找然后erase。
}
static CollisionMap &theCollisionMap() {
static CollisionMap collisionMap;
return collisionMap;
}
private:
CollisionMap() {}
CollisionMap(const CollisionMap&) {}
};
void shipAsteriod(GameObject &spaceShip, GameObject &asteriod) {
}
void shipStation(GameObject&spaceShip, GameObject &spaceStation) {
}
void asteriodStation(GameObject&asteriod, GameObject&spaceStation) {
}
void asteriodShip(GameObject&asteriod, GameObject&spaceShip) {
shipAsteriod(spaceShip, asteriod);
}
void stationShip(GameObject&spaceStation, GameObject&spaceShip) {
shipStation(spaceShip, spaceStation);
}
void stationAsteriod(GameObject&spaceStaion, GameObject&asteriod) {
asteriodStation(asteriod, spaceStaion);
}
int main(void) {
CollisionMap::theCollisionMap().addEntry("SpaceShip", "Asteriod", &shipAsteriod);
}
这个版本写的并不全,只是点到那个意思,把那些HitMap信息都封装在一个类里,然后通过外部接口对map进行添加,删除和修改,这只是对信息表的操作,并不是对整个碰撞实验。声明一点:这个过程只需要一个CollisionMap对象,所以采用了将缺省构造和拷贝构造私有化的操作,用外部接口调用构造函数实现构造的操作。
看完这三个版本,我是懵圈的,这东西很长,而且有的地方不好理解,但总的讲这部分觉得还是比较重要的,要不然也不会是四十多页的篇幅讲述这个问题,粗略的看这些东西采用的是自行模拟虚表的方法,至于方法是用map来存储键值对,通过map匹配响应函数,然后用函数指针调用相应的碰撞处理函数。
ps:还有一个小问题困扰了我,这完全是我的顺手失误,就是关于dynamic_cast 转型的问题,正常的做法是:
devired d;
base *p=d;
devired* ptr=dynamic_cast<devired*>(p);
devired &ptr=dynamic_cast<devired&>(*p);
结果我写成了
devired ptr=dynamic_cast<devired&>(*p);
怎么测都有问题,真的是很sui