您的位置:首页 > 编程语言 > C语言/C++

条款三十一:让函数根据一个以上的对象类型来决定如何虚化

2017-01-04 01:35 393 查看

条款三十一:让函数根据一个以上的对象类型来决定如何虚化

  假设我们决定写一个视频游戏软件,场景中涉及到太空飞船,太空站,小行星等。在空间中可能发生下面四种碰撞结果:

太空飞船以低速碰撞太空站,则安全泊入,否则二者所受损害和太空飞船的速度成正比;

太空飞船和太空飞船或者太空站和太空站碰撞,则二者所受损害和自己的的速度成正比;

小号的行星和太空飞船或者太空站碰撞,小行星损毁,如果是大号的小行星则太空飞船和太空站损毁;

小行星和小行星碰撞,则分裂成更小的行星散开。

因为三者都有共同的属性,我们可以为他们构建一个共同的抽象基类。



class GameObject{};
class SpaceStation:public GameObject{};
class SpaceShip:public GameObject{};
class Asteroid:public GameObject{};


我们可能建立的碰撞处理函数:


void checkCollision(GameObject& ob1, GameObject& ob2)
{
if(theyJustCollision(ob1, ob2))
{
processCollision(ob1, ob2);
}
else
{
//TODO:...
}
}


  但是上面的第吗无法ob1和ob2具体的类型只知道他们是GameObject。因此我们可以使用其他方式实现。

一、虚函数+RTTI(运行时类型识别)

  我们可以为GameObject提供一个虚函数,供不同的派生类处理碰撞事件,最一般的处理方法是通过嵌套的if-then处理:

class GameObject
{
public:
virtual void collision(GameObject& otherOb) = 0;
};

class SpaceShip:public GameObject
{
public:
virtual void collision(GameObject& otherOb);
}

class CollisionWithUnknownObject        //当和未知物体碰撞抛出异常
{
public:
CollisionWithUnknownObject(GameObject& otherOb);
};

void SpaceShip::collision(GameObject& otherOb)
{
const type_info& obType = typeid(otherOb);
if(obType == typeid(SpaceShip))
{
SpaceShip& ptr = static_cast<SpaceShip&>(otherOb);
//Process the collision
}

if(obType == typeid(SpaceStation))
{
SpaceStation& ptr = static_cast<SpaceStation&>(otherOb);
//Process the collision
}

if(obType == typeid(Asteroid))
{
Asteroid& ptr = static_cast<Asteroid&>(otherOb);
//Process the collision
}

throw CollisionWithUnknownObject(otherOb);
}


  这种处理方式有一种C语言过程设计的味道,并不像C++面向对象的风格,而且需要我们考虑到所有的情况,这对于维护来说并不友好。

二、只使用虚函数

  可以使用下面的方法解决上述问题:

class SpaceShip;
class SpaceStation;
class Asteroid;
class GameObject
{
public:
virtual void collide(GameObject& otherOb) = 0;
virtual void collide(SpaceShip& otherOb) = 0;
virtual void collide(SpaceStation& otherOb) = 0;
virtual void collide(Asteroid& otherOb) = 0;
};

class SpaceShip:public GameObject
{
public:
virtual void collide(GameObject& otherOb);
virtual void collide(SpaceShip& otherOb);
virtual void collide(SpaceStation& otherOb);
virtual void collide(Asteroid& otherOb);
};

void SpaceShip::collide(GameObject& otherOb)
{
otherOb.collide(*this);
}


  上述代码相对来说很简单,尽管他能解决我们遇到的问题,但是当你的设计中需要添加其他对象时,你就必须修改原来的类来适应新的情况,这对于一个工程来说很糟糕,因此使用时尽量保证你的设计的对象基本不会修改。

三、自行仿真虚函数表格(Virtual Function Tables)

  在之前的章节我们说过类中的虚函数表,这里我们可以利用虚函数表的原理为我们的碰撞处理函数建立一个表,通过查找表来实现相应的功能。

class GameObject
{
public:
virtual void collide(GameObject& otherOb) = 0;
//TODO:
}

class SpaceShip:public GameObject
{
public:
virtual void collide(GameObject& otherOb);
virtual void hitSpaceStation(SpaceStation& otherOb);
virtual void hitSpaceShip(SpaceShip& otherOb);
virtual void hitAsteroid(Asteroid& otherOb);
//TODO:
private:
typedef void(SpaceShip::*HitFunctionPtr)(GameObject&);
typedef std::map<string, HitFunctionPtr> hitMap;
static HitFunctionPtr lookup(const GameObject& whatWeHit);
};

void SpaceShip::collide(GameObject& otherOb)
{
HitFunctionPtr ptr = lookup(otherOb);
if(ptr)
{
(this->*ptr)(otherOb);
}
else
{
throw CollisionWithUnknownObject(otherOb);
}
}

SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collideMap;
HitMap::iterator mapIt = collideMap.find(typeid(whatWeHit).name());
if(mapIt == collideMap.end())
{
return 0;
}

return (*mapIt).second;
}


  上述代码通过collide筛选需要执行的方法通过lookup函数在map映射中寻找我们需要用到的方法进行处理,上述代码并不是最终版本因为map并未进行初始化,只是描述了如何使用。

四、将自行仿真的虚函数表初始化

  进行初始化时我们不能直接给map添加值,因为map中的值和函数最终期望的导致虽然是同一个基类的派生,但是始终不是同一个类,因此可能引发错误。,因此通过改变函数参数,再将指针进行转换即可。

class GameObject
{
public:
virtual void collide(GameObject& otherOb) = 0;
};

class SpaceShip:public GameObject
{
private:
static HitMap* initializeCollisionMap();
public:
virtual void collide(GameObject& otherOb);
virtual void hitSpaceShip(GameObject& otherOb);
virtual void hitSpaceStation(GameObject& otherOb);
virtual void hitAsteroid(GameObject& otherOb);
};

SpaceShip::HitMap* SpaceShip::initializeCollisionMap()
{
HitMap* ptr = new HitMap;
(*ptr)["SpaceShip"] = &hitSpaceShip;
(*ptr)["SpaceStation"] = &hitSpaceStation;
(*ptr)["Asteroid"] = &hitAsteroid;

return ptr;
}

void SpaceShip::hitSpaceShip(GameObject& otherOb)
{
SpaceShip& ptr = dynamic_cast<SpaceShip&>(otherOb);
//TODO:
}

void SpaceShip::hitSpaceStation(GameObject& otherOb)
{
SpaceStation& ptr = dynamic_cast<SpaceStation&>(otherOb);
//TODO:
}

void SpaceShip::hitAsteroid(GameObject& otherOb)
{
Asteroid& ptr = dynamic_cast<Asteroid&>(otherOb);
//TODO:
}


五、使用分成员函数的碰撞处理函数

  前面提到了如果新增类就必须修改代码的问题,上述代码似乎没有解决这个问题,当然我们并不希望只因对象的增加就修改代码类主体。如果使用分成员函数处理就可以解决这个问题还可以解决之前我们忽略的问题——该由谁来处理碰撞事件。

#include "SpaceShip.h"
#include "SpaceStation.h"
#include "SpaceAsteroid.h"

namespace
{
void shipAsteroid(GameObject& spaceShip, GameObject& asteroid);
void shipStation(GameObject& spaceShip, GameObject& spaceStation);
void asteroidStation(GameObject& asteroid, GameObject& spaceStation);

//为了实现对称性
void asteroidShip(GameObject& asteroid, GameObject& spaceShip);
void stationShip(GameObject& spaceStation, GameObject& spaceShip);
void stationAsteroid(GameObject& spaceStation, GameObject& asteroid);

typedef void(*HitFunctionPtr)(GameObject&, GameObject&);
typedef map<pair<string, string>, HitFunctionPtr> hitMap;
pair<string, string> makeStringPtr(const char* str1, const char* str2);
hitMap* initializeCollisionMap();
HitFunctionPtr lookup(const string& ob1, const string ob2);
}//namespace

void processCollision(GameObject& ob1, GameObject& ob2)
{
HitFunctionPtr ptr = lookup(typeid(ob1).name(), typeid(ob2).name());
if(ptr)
{
ptr(ob1, ob2);
}
else
{
throw unKnownCollision(ob1, bo2);
}
}

namespace
{
pair<string, string> makeStringPtr(const char* str1, const char* str2)
{
return pair<string, string>(str1, str2);
}
}

namespace
{
hitMap* initializeCollisionMap()
{
hitMap* ptr = new hitMap;
(*ptr)[makeStringPtr("SpaceShip", "Asteroid")] = &shipAsteroid;
(*ptr)[makeStringPtr("SpaceShip", "SpaceStation")] = &shipStation;
//TODO:
}

return ptr;
}

namespace
{
HitFunctionPtr lookup(const string& ob1, const string& ob2)
{
static auto_ptr<hitMap> collisionMap(initializeCollisionMap());
hitMap::iterator it = collisionMap->find(make_pair(ob1, ob2));
if(it == collisionMap->end())
{
return 0;
}

return (*it).second;
}
}


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

  上述表述中一直说的是单个继承的子类,如果有下面的继承关系呢?



  假如调用我们设计的函数就会发现结果并不如愿,尽管militaryShip被看作SpaceShip但是lookup函数并不知道这些。这种时候我们只能使用之前的双虚函数,的确很难堪。

七、将自行仿真的虚函数表初始化(again)

  对于这块我没看太懂,就只贴代码

class CollisionMap
{
public:
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
void addPtr(const string& type1, const string& type2, HitFunctionPtr collisionFunction, bool symmetric = true);
void removePtr(const string& type1, const string& type2);
HitFunctionPtr lookup(const string& type1, const string& type2);
static CollisionMap& theCollisionMap();
private:
CollisionMap();
CollisionMap(const CollisionMap&);
};

class RegisterCollisionFunction
{
public:
RegisterCollisionFunction(const string& type1, const string& type2, CollisionMap::HitFunctionPtr collisionFunction, bool symmetric = true)
{
CollisionMap::theCollisionMap().addPtr(type1, type2, collisionFunction, symmetric);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++
相关文章推荐