【C++ techniques】让函数根据一个以上的对象类型来决定如何虚化

写一个视频游戏软件,涉及到宇宙飞船、太空站、小行星等天体:

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(运行时期类型辨别)

//声明一个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来仿真虚函数:

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函数:

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运算,并得以在所有的虚函数调用端产生相同代码,用以:

  1. 决定正确的vtbl索引;
  2. 调用vtbl中的索引位置内所指的函数。
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;
}
//中介函数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)初始化

//一个不正确的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返回的值,避免资源泄露问题:
	...
}
//函数指针的参数均设为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)函数”的碰撞处理函数

#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);
}

继承+自行仿真的虚函数表格

扩大继承体系时,必须要求每个人都重新编译。

将自行仿真的虚函数表格初始化(再度讨论)

//利用一个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为基础的实现品。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值