分类: C/C++
2009-08-26 15:35:34
l 初始化模拟虚函数表(再次讨论)
这就是关于二重调度的所有要说的,但是,用如此悲观的条款来结束是令人很不愉快的。因此,让我们用概述初始化collisionMap的两种方法来结束。
按目前情况来看,我们的设计完全是静态的。每次我们注册一个碰撞处理函数,我们就不得不永远留着它。如果我们想在游戏运行过程中增加、删除或修改碰撞处理函数,将怎么样?不提供。
但是是可以做到的。我们可以将映射表放入一个类,并由它提供动态修改映射关系的成员函数。例如:
class CollisionMap {
public:
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
void addEntry(const string& type1,
const string& type2,
HitFunctionPtr collisionFunction,
bool symmetric = true); // see below
void removeEntry(const string& type1,
const string& type2);
HitFunctionPtr lookup(const string& type1,
const string& type2);
// this function returns a reference to the one and only
// map — see Item 26
static CollisionMap& theCollisionMap();
private:
// these functions are private to prevent the creation
// of multiple maps — see Item 26
CollisionMap();
CollisionMap(const CollisionMap&);
};
这个类允许我们在映射表中进行增加和删除操作,以及根据类型名对查找相应的碰撞处理函数。它也使用了Item E26中讲的技巧来限制CollisionMap对象的个数为1,因为我们的系统中只有一个映射表。(更复杂的游戏需要多张映射表是可以想象到的。)最后,它允许我们简化在映射表中增加对称性的碰撞(也就是说,类型T1的对象撞击T2的对象和T2的对象撞击T1的对象,其效果是相同的。)的过程,它自动增加对称的映射关系,只要addEntry被调用时可选参数symmetric 被设为true。
借助于CollisionMap类,每个想增加映射关系的用户可以直接这么做:
void shipAsteroid(GameObject& spaceShip,
GameObject& asteroid);
CollisionMap::theCollisionMap().addEntry("SpaceShip",
"Asteroid",
&shipAsteroid);
void shipStation(GameObject& spaceShip,
GameObject& spaceStation);
CollisionMap::theCollisionMap().addEntry("SpaceShip",
"SpaceStation",
&shipStation);
void asteroidStation(GameObject& asteroid,
GameObject& spaceStation);
CollisionMap::theCollisionMap().addEntry("Asteroid",
"SpaceStation",
&asteroidStation);
...
必须确保在发生碰撞前就将映射关系加入了映射表。一个方法是让GameObject的子类在构造函数中进行确认。这将导致在运行期的一个小小的性能开销。另外一个方法是创建一个RegisterCollisionFunction 类:
class RegisterCollisionFunction {
public:
RegisterCollisionFunction(
const string& type1,
const string& type2,
CollisionMap::HitFunctionPtr collisionFunction,
bool symmetric = true)
{
CollisionMap::theCollisionMap().addEntry(type1, type2,
collisionFunction,
symmetric);
}
};
用户于是可以使用此类型的一个全局对象来自动地注册他们所需要的函数:
RegisterCollisionFunction cf1("SpaceShip", "Asteroid",
&shipAsteroid);
RegisterCollisionFunction cf2("SpaceShip", "SpaceStation",
&shipStation);
RegisterCollisionFunction cf3("Asteroid", "SpaceStation",
&asteroidStation);
...
int main(int argc, char * argv[])
{
...
}
因为这些全局对象在main被调用前就构造了,它们在构造函数中注册的函数也在main被调用前就加入映射表了。如果以后增加了一个派生类
class Satellite: public GameObject { ... };
以及一个或多个碰撞处理函数
void satelliteShip(GameObject& satellite,
GameObject& spaceShip);
void satelliteAsteroid(GameObject& satellite,
GameObject& asteroid);
这些新函数可以用同样方法加入映射表而不需要修改现存代码:
RegisterCollisionFunction cf4("Satellite", "SpaceShip",
&satelliteShip);
RegisterCollisionFunction cf5("Satellite", "Asteroid",
&satelliteAsteroid);
这不会改变实现多重调度没有完美解决方法的事实。但它使得容易提供数据给基于map的实现,如果我们认为这种实现最接近我们的需要的话。
l 注11:
要指出的是,不是那么可完全确定的。C++标准并没有规定type_info::name的返回值,不同的实现,其行为会有区别。(例如,对于类Spaceship,type_info::name的一个实现返回“class SpaceShip”。)更好的设计是通过它所关联的type_info对象的地址了鉴别一个类,因为每个类关联的type_info对象肯定是不同的。HitMap于是应该被申明为map
我们现在到了接近结束的部分了,这章讲述的是一些不属于前面任一章节的指导原则。开始两个是关于C++软件开发的,描述的是设计适应变化的系统。面向对象的一个强大之处是支持变化,这两个条款描述具体的步骤来增强你的软件对变化的抵抗能力。
然后,我们分析怎么在同一程序中进行C和C++混合编程。这必然导致考虑语言学之外的问题,但C++存在于真实世界中,所以有时我们必须面对这种事情。