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

原创 2017年01月04日 01:35:09

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

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

  • 太空飞船以低速碰撞太空站,则安全泊入,否则二者所受损害和太空飞船的速度成正比;
  • 太空飞船和太空飞船或者太空站和太空站碰撞,则二者所受损害和自己的的速度成正比;
  • 小号的行星和太空飞船或者太空站碰撞,小行星损毁,如果是大号的小行星则太空飞船和太空站损毁;
  • 小行星和小行星碰撞,则分裂成更小的行星散开。

    因为三者都有共同的属性,我们可以为他们构建一个共同的抽象基类。
    这里写图片描述

    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);
        }
    }
版权声明:本文为博主原创文章,未经博主允许不得转载。

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

普通函数重载 #include using namespace std; class SpaceShip; class SpaceStation; class Asteriod; class Gam...

item31让函数根据一个以上的对象类型来决定如何虚化

#include /* >如果宇宙飞船以低速与太空站碰撞,宇宙飞船会泊进太空站(程序没有涉及) 否则宇宙飞船和太空站受到的损害与其速度成正比 >如果宇宙飞船与宇宙飞船碰撞,或是...

c++ 将构造函数虚化,动态产生对象

我们知道构造函数有一个必须遵守的规则,即构造函数不能定义为虚函数。但有一个具体的应用是要求是在不同的场景下通过一个指针或者引用生成不同的对象,这就类似于类型的动态生成,即在执行期才能确定具体的对象。这...
  • dqjyong
  • dqjyong
  • 2012年09月13日 21:21
  • 1151

条款二十五:将constructer和nonmember function虚化

条款二十五:将constructer和nonmember function虚化1. 虚化构造函数   当看到将构造函数虚化时你可能想到C++语法不是只支持析构函数的虚化而不允许构造函数的虚化吗?其...

effective c++条款13-17 “以对象管理资源”之C++类型转换函数和构造函数

其实我们已经在C/C++中见到过多次标准类型数据间的转换方式了,这种形式用于在程序中将一种指定的数据转换成另一指定的类型,也即是强制转换,比如:int a = int(1.23),其作用是将1.23转...

技术(1)—构造函数和非成员函数的虚化

Item 25 将construtor和non-member functions虚化。 所谓virtual constructor是某种函数,视其获得的输入,可产生不同类型的对象。上例中的readC...

一个函数锁住相同类型的多个对象造成的死锁

一个类型中有个互斥量变量,一个函数锁住这个类型的多个对象,由于编码不注意加锁顺序在多线程环境下造成了死锁。 #include #include #include #include using n...

条款24:若所有参数都需要类型转换,请为此采用non-member函数

如果你需要为某个函数的所有参数(包括this指针所的那个隐喻参数)进行类型转换,那么这个函数必须是non-member 例子: class Rational { public: ...

条款5:对定制的“类型转换函数”保持警觉

什么是类型转换函数? 1)转换构造函数可以将一个指定类型的数据转换为类的对象 2)类型转换函数的作用是将一个类的对象转换成另一类型的数据 3)一个类中同时又转换构造函数和类型转换函数将产生二义性...

Effecticve学习笔记_条款45:运用成员函数模板接收所有兼容类型

假设有一个基类和一个派生类像下面这样:class Base {}; class Derived : public Base {};  由于类间的上行转换时安全的,我们可以得到如下的正确结果:Deriv...
  • zjwson
  • zjwson
  • 2016年08月26日 11:44
  • 272
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:条款三十一:让函数根据一个以上的对象类型来决定如何虚化
举报原因:
原因补充:

(最多只允许输入30个字)