zoukankan      html  css  js  c++  java
  • More Effective C++: 05技术(30-31)

    30:Proxy classes 代理类

             在C++中使用变量作为数组大小是违法的,也不允许在堆上分配多维数组:

    int data[dim1][dim2]; 
    int *data = new int[dim1][dim2]; // error!

             为了弥补上述缺点,可以设计一个二维数组类:

    template<class T>
    class Array2D {
    public:
        Array2D(int dim1, int dim2);
        ...
    };
    
    Array2D<int> data(10, 20);
    Array2D<float> *data = new Array2D<float>(10, 20);
    void processInput(int dim1, int dim2)
    {
        Array2D<int> data(dim1, dim2);
        ...
    }

             因为没有operator[][]这样的操作符,为了能以data[3][6]的形式访问该二维数组,所以,这里需要使用proxy类:

    template<class T>
    class Array2D  {
    public:
        Array2D(int dim1, int dim2){
            m_dim1 = dim1;
            m_dim2 = dim2;
            data = new T[dim1*dim2];
        }
        ~Array2D(){delete data;}
        class Array1D  {
        public:
            Array1D(T *data, int begin){
                innerdata = data+begin;
            }
            T& operator[](int index);
            const T& operator[](int index) const;
        private:
            T *innerdata;
        };
        Array1D operator[](int index);
        const Array1D operator[](int index) const;
    private:
        T *data;
        int m_dim1, m_dim2;
    };
    
    template<class T>
    typename Array2D<T>::Array1D Array2D<T>::operator[](int index){
        return Array2D::Array1D(data, index * m_dim2);
    }
    
    template<class T>
    const typename Array2D<T>::Array1D Array2D<T>::operator[](int index) const{
        return Array2D::Array1D(data, index * m_dim2);
    }
    
    template<class T>
    T& Array2D<T>::Array1D::operator[](int index){
        return innerdata[index];
    }
    
    template<class T>
    const T& Array2D<T>::Array1D::operator[](int index) const{
        return innerdata[index];
    }
    
    int dim1 = 3, dim2 = 4;
    Array2D<int> array(dim1, dim2);
    array[0][1] = 1;
    array[0][2] = 2;
    array[1][0] = 10;
    array[2][3] = 23;

             每个Array1D对象表示一个一维数组,而        Array2D的用户不需要知道Array1D的存在。注意上述模板内定义嵌套类的函数定义写法。

             利用proxy类,还可以实现区分operator[]读写操作。上一章讲述的引用计数的String类,其operator[]可以用来读字符,也可以用来写字符。读操作是所谓的右值引用;写操作是左值引用。虽然编译器无法告诉我们operator[]到底是用来读还是写,但是只要将所要处理的动作放缓,直到operator[]的返回结果被使用为止。可以修改operator[],使其返回字符串中字符的proxy,就可以看见该proxy如何被使用:

    class String {
    public:
        String(const char *value = "");
        
        class CharProxy {
        public:
            CharProxy(String& str, int index);
            CharProxy& operator=(const CharProxy& rhs);   //  左值引用
            CharProxy& operator=(char c);
            operator char() const; //  右值引用
        private:
            String& theString;
            int charIndex;
        };
        const CharProxy operator[](int index) const;
        CharProxy operator[](int index);
    
    friend class CharProxy;
    
    private:
        struct StringValue: public RCObject  {
            char *data;
            StringValue(const char *initValue);
            StringValue(const StringValue& rhs);
            void init(const char *initValue);
            ~StringValue();
        };
    
        RCPtr<StringValue> value;
    };

             有了上面的代码,考虑这条语句:cout<<s1[5],s1[5]产生一个CharProxy对象,但是该对象没有定义output操作符,所以编译器需要找一个合适的隐式类型转换:将CharProxy隐式转换为char,然后进行输出即可。

             而对于s1[5]=’x’,s1[5]返回CharProxy,调用CharProxy::operator=函数即可,这时,被赋值的CharProxy对象被用来作为一个左值。同样的,s1[5]=s1[8]也是一样。

             String的operator[]代码如下:

    const String::CharProxy String::operator[](int index) const{
        return CharProxy(const_cast<String&>(*this), index);
    }
    String::CharProxy String::operator[](int index){
        return CharProxy(*this, index);
    }

    注意const operator[]返回一个const CharProxy,由于CharProxy::operator=不是一个 const成员函数,const CharProxy因此不能被用来做为赋值的目标物。因此不论是const operator[]  所传回的 proxy,或是该proxy所代表的字符,都不能被用来做为左值。这正是我们希望 const operator[]所具备的行为。

    另外,当const operator[] 返回CharProxy对象时,需要对其进行const_cast转换,去除const属性,这是因为CharProxy的构造函数只接受non-const String。

    operator[]返回的每一个CharProxy都会记住它所附属的字符串,以及它所在的索引位置,以便将来进行读取或赋值:

    String::CharProxy::CharProxy(String& str, int index)
    : theString(str), charIndex(index) {}

             CharProxy的char转换函数如下:

    String::CharProxy::operator char() const{
        return theString.value->data[charIndex];
    }

           该函数以by value的方式返回一个字符,由于C++ 限制只能在右值情境下使用这样的返回值,所以这个转换函数只能用于右值合法之处。

             接下来是CharProxy的operator=操作符:

    String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs){
        if (theString.value->isShared()) {
            theString.value = new StringValue(theString.value->data);
        }
    
        theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex];
        return *this;
    }
    
    String::CharProxy& String::CharProxy::operator=(char c){
        if (theString.value->isShared()) {
            theString.value = new StringValue(theString.value->data);
        }
        theString.value->data[charIndex] = c;
        return *this;
    }

             与上一章中的non-const String::operator[]比较,会发现它们非常类似。在上一章中悲观地假设所有non-const operator[]的调用都是为了写操作,此处们把完成“写操作”的代码转移到CharProxy的operator=中,避免了non-const operator[]在右值情境下也需付出写操作的昂贵代价。

             尽管我们希望proxy对象能够无间隙的代表它所表示的对象,但是这种想法很难达成,因为除了赋值之外,对象还有很多其他操作,比如:char *p = &s1[5],这条语句有两个报错,s1[5]返回一个临时CharProxy对象,既不能取临时对象的地址,也无法将CharProxy*赋值给char *,这种情况下,需要重载operator&:

    const char * String::CharProxy::operator&() const{
        return &(theString.value->data[charIndex]); 
    }
    
    char * String::CharProxy::operator&(){
        if (theString.value->isShared()) {
            theString.value = new StringValue(theString.value->data);
        }
    
        theString.value->markUnshareable();
        return &(theString.value->data[charIndex]);
    }

             non-const版本需要返回一个指针,指向一个可能被修改的字符,因此,需要将StringValue成为其专属副本。

             这还仅仅是取地址,原始对象可能还支持+=,++等操作,这些需要左值的操作想要成功,必须一一为proxy类定义相应的操作符(CharProxy转换为char的操作符,返回的是右值,因此无法使用这些操作符)。

             另外,如果proxy代表的对象具有成员函数,则为了能使proxy看起来像原始对象一样能调用相应的成员函数,也需要在proxy中定义相应的函数。

             另外,proxy虽然能隐式转换为它所代表的对象,但是这种隐式转换只能发生一次,如果原始对象A能隐式转换为B,则在需要参数B的函数调用中可以使用A,但不能使用A的proxy。

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

             假设你在写一个游戏,游戏中需要处理宇宙飞船、太空站、小行星等的碰撞问题,不同物体之间的碰撞需要做不同的处理。于是有下面的代码:

    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 {
            ...
        }
    }

             processCollision需要根据object1和object2的具体类型做不同的处理。这就是所谓的double-dispatching问题。在面向对象程序设计社区,人们把一个“虚函数调用动作”称为一个"message dispatch"。因此某个函数调用如果根据两个参数而虚化,自然而然地就被称为   "double dispatch"。更广泛的情况则被称为multiple dispatch。

            

             最一般化的double-dispatching实现,就是利用虚函数和RTTI:

    class GameObject {
    public:
        virtual void collide(GameObject& otherObject) = 0;
        ...
    };
    class SpaceShip: public GameObject {
    public:
        virtual void collide(GameObject& otherObject);
        ...
    };
    
    class CollisionWithUnknownObject {
    public:
        CollisionWithUnknownObject(GameObject& whatWeHit);
        ...
    };
    void SpaceShip::collide(GameObject& otherObject){
        const type_info& objectType = 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<SpaceStation&>(otherObject);
            process a SpaceShip-SpaceStation collision;
        }
        else if (objectType == typeid(Asteroid)) {
            Asteroid& a = static_cast<Asteroid&>(otherObject);
            process a SpaceShip-Asteroid collision;
        }
        else {
            throw CollisionWithUnknownObject(otherObject);
        }
    }

             上面这段代码的缺点,collide函数必须知道其每一个兄弟类(所有继承自GameObject的那些类)。如果有新的类型加入了这个游戏,就必须修改程序中每一个可能遭遇新对象的RTTI-based if-then-else链,这会造成程序难以维护。

             还可以只用虚函数就解决这个问题:

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

             比较有意思的是接收GameObject&的那个collide函数,当对象的静态类型是GameObject时调用该函数,函数内部,根据参数的动态类型决定调用该类型的哪个虚函数,而函数参数为*this,也就是个SpaceShip类型。

             这种做法的缺点跟上面使用RTTI的类似,每个类都必须知道其兄弟类,一旦有新的类加入,代码就必须修改。而这里的修改又与RTTI解法不同,RTTI解法中,只需要修改每个类的实现部分,也就是collide中的if-else-then,而此处需要修改类的定义,增加一个新的虚函数。然而你并不一定有机会或者权利去修改类的定义式。

             简而言之,如果你需要在你的程序中实现double-dispatching,最好的方向就是修改设计,消除此项需求。如果不能,那么,虚拟函式法比RTTI 法安全一些,但是如果你对头文件的权力不够,这种作法会束缚你的系统扩充性。至于RTTI法,虽不需要重新编译,却往往导至软件难以维护。你总是得付出代价,才能获得机会。

             之前说过,虚函数是通过vtbl实现的。在这个double- dispatching场景中,我们可以自己实现一个vtbl,这比RTTI-based会更有效率,而且可以将RTTI的使用集中在vtbl初始化的地方。

    class GameObject {
    public:
        virtual void collide(GameObject& otherObject) = 0;
        ...
    };
    
    class SpaceShip: public GameObject {
    public:
        virtual void collide(GameObject& otherObject);
        virtual void hitSpaceShip(SpaceShip& otherObject);
        virtual void hitSpaceStation(SpaceStation& otherObject);
        virtual void hitAsteroid(Asteroid& otherobject);
        ...
    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 CollisionWithUnknownObject(otherObject);
        }
    }
    
    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;
    }

             这里没有将collide重载,而是用函数名区分不同对象之间的碰撞,原因稍后就会解释。

             在SpaceShip::collide函数内,调用lookup函数,根据GameObject参数查找合适的成员函数指针,一旦找到则调用即可,找不到的时候抛出异常。

             现在考虑lookup函数的实现,在lookup中,需要一个关系型数组,lookup查找该数组,根据对象类型得到某个成员函数指针。这个关系型数组应该在使用之前就产生并初始化了,并在不再需要时进行销毁,可以使用new和delete来产生和销毁数组,但那样容易发生错误。为了保证数组会在使用之前进行初始化,比较好的解决办法就是让关系型数组成为 lookup内的static对象,只有在lookup第一次调用时它才会被产生,而在main结束之后它才会被摧毁:

    class SpaceShip: public GameObject {
    private:
        typedef map<string, HitFunctionPtr> HitMap;
        ...
    };
    
    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;
    }

             这里根据typeid(whatWeHit).name()查找collisionMap,但是标准并未明确规定type_info::name的返回值,不同的编译器可能会有不同的行为,比如对于SpaceShip类,有的编译器可能就会返回"class SpaceShip"。一个更好的设计是,以type_info对象的地址来识别class,因为它绝对是独一无二的,此时HitMap类型应该是map<const type_info*, HitFunctionPtr>。

             现在,唯一的问题就是collisionMap的初始化问题了,可以将其初始化放进一个名为initializeCollisionMap的private static成员函数中:

    SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit)
    {
        static HitMap collisionMap = initializeCollisionMap();
        ...
    }

             但是这种方法会有map的复制成本,因此,这里最高改为指针,为了不操心delete指针的问题,可以使用智能指针:

    SpaceShip::HitFunctionPtr SpaceShip::lookup(const GameObject& whatWeHit){
        static unique_ptr<HitMap> collisionMap(initializeCollisionMap());
        ...
    }
    
    SpaceShip::HitMap * SpaceShip::initializeCollisionMap(){
        HitMap *phm = new HitMap;
        (*phm)["SpaceShip"] = &hitSpaceShip;
        (*phm)["SpaceStation"] = &hitSpaceStation;
        (*phm)["Asteroid"] = &hitAsteroid;
        return phm;
    }

             这个初始化代码还有最后一个问题,map中的value类型是:typedef void (SpaceShip::*HitFunctionPtr)(GameObject&),这种函数的参数为GameObject,但是hitSpaceShip、hitSpaceStation、hitAsteroid它们的参数分别为 SpaceShip、SpaceStation、Asteroid。虽然它们都可以隐式转换为GameObject,但是函数指针之前却不存在这种转换。

             你可能觉得下面的方法可以解决这个问题:

    //  一个坏主意…
    SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
    {
        HitMap *phm = new HitMap;
        (*phm)["SpaceShip"] = reinterpret_cast<HitFunctionPtr>(&hitSpaceShip);
        (*phm)["SpaceStation"] = reinterpret_cast<HitFunctionPtr>(&hitSpaceStation);
        (*phm)["Asteroid"] = reinterpret_cast<HitFunctionPtr>(&hitAsteroid);
        return phm;
    }

             这是个坏主意,因为它欺骗了编译器,一旦满足某种条件,编译器就会对这种欺骗进行报复:如果SpaceStation、SpaceShip或Asteroid有GameObject之外的其他基类,你可能会发现,你在collide中对碰撞处理函式的呼叫,会导至相当粗鲁的行为。比如下面是一个具有菱形继承的类D:

     

    D对象内的四个“基类成份”,每一个都有不同地址。虽然指针和引用的行为不同,但编译器通常是以指针来实现引用的。因此,当对象拥有多个基类,并以引用的方式传递给函数时,编译器是否传递了正确的地址(此地址对应于被调用函数的参数类型),将是非常重要的关键。

    如果你欺骗编译器,告诉它你的函数期望获得一个GameObject,而其实它真正期望获得的是个SpaceShip,当你调用那个函数,编译器就会传递错误的地址,导至执行时期可怕的大屠杀。这种问题很难找出原因。转型令人沮丧,原因有许多个,这是其中之一。

    因此,只能改变hitSpaceShip这些成员函数的原型,使他们接受GameObject对象:

    class SpaceShip: public GameObject {
    public:
        virtual void collide(GameObject& otherObject);
        virtual void hitSpaceShip(GameObject&  spaceShip);
        virtual void hitSpaceStation(GameObject&  spaceStation);
        virtual void hitAsteroid(GameObject&  asteroid);
        ...
    };

    这就是为什么没有重载collide函数的原因。因为参数都一样了。

    下面是剩下的代码:

    SpaceShip::HitMap * SpaceShip::initializeCollisionMap(){
        HitMap *phm = new HitMap;
        (*phm)["SpaceShip"] = &hitSpaceShip;
        (*phm)["SpaceStation"] = &hitSpaceStation;
        (*phm)["Asteroid"] = &hitAsteroid;
        return phm;
    }
    
    void SpaceShip::hitSpaceShip(GameObject&  spaceShip){
        SpaceShip& otherShip=dynamic_cast<SpaceShip&>(spaceShip);
        process a SpaceShip-SpaceShip collision;
    }
    
    void SpaceShip::hitSpaceStation(GameObject&  spaceStation){
        SpaceStation& station=dynamic_cast<SpaceStation&>(spaceStation);
        process a SpaceShip-SpaceStation collision;
    }
    
    void SpaceShip::hitAsteroid(GameObject&  asteroid){
        Asteroid& theAsteroid = dynamic_cast<Asteroid&>(asteroid);
        process a SpaceShip-Asteroid collision;
    }

    在hitSpaceShip这样的函数中,需要将参数对象使用dynamic_cast强制转换为真正的类型。

    实际上,上面这种自行实现vtbl的解法依然有类似的问题,因为针对每一个兄弟类,都需要有一个成员函数处理碰撞。一旦有新的GameObject类型加入到这个游戏中,还是需要修改类的定义。

    如果关系型数组内含的指针指向的是non-member functions,重新编译的问题便可消除:

    #include "SpaceShip.h"
    #include "SpaceStation.h"
    #include "Asteroid.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)
        { shipAsteroid(spaceShip, asteroid); }
        void stationShip(GameObject& spaceStation, GameObject& spaceShip)
        { shipStation(spaceShip, spaceStation); }
        void stationAsteroid(GameObject& spaceStation, GameObject& asteroid)
        { asteroidStation(asteroid, spaceStation); }
    
        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 UnknownCollision(object1, object2);
    }

    这里使用了匿名namespace,匿名namespace内的所有东西对其所在的编译单元而言都是私有的,也就是说,其效果好像是在文件中将函数声明为static一样,更推荐使用匿名namespace。

    剩下的代码如下:

    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","Asteroid")] = &shipAsteroid;
            (*phm)[makeStringPair("SpaceShip", "SpaceStation")] = &shipStation;
            ...
            return phm;
        }
    }
    
    namespace  {
        HitFunctionPtr lookup(const string& class1, const string& class2) {
            static unique_ptr<HitMap> collisionMap(initializeCollisionMap());
    
            HitMap::iterator mapEntry = collisionMap->find(make_pair(class1, class2));
            if (mapEntry == collisionMap->end()) return 0;
            return (*mapEntry).second;
        }
    }

    现在,一旦有新的GameObject子类加进这个继承体系中,原有的类不再需要重新编译,也不需维护纠葛混乱的“以 RTTI 为基础的”switch 或 if-then-else。如果新的类加入到GameObject的继承体系中,只要其本身定义良好;我们的系统只需在initializeCollisionMap 内调整代码,并在“与processCollision相应的那个匿名namespace”内增加新碰撞处理函数。

    还有一个问题,如果需要满足inheritance-based类型转换,比如现在宇宙飞船需要区分商业宇宙飞船和军事宇宙飞船:SpaceShip现在派生出了CommercialShip和MilitaryShip,而他们与原有对象的碰撞处理与SpaceShip完全相同,因此如果有一个MilitaryShip和一个Asteroid碰撞,我们希望调用的是void shipAsteroid(GameObject& spaceShip, GameObject& asteroid)。但是就目前的代码而言,这种情况下实际上会抛出一个UnknownCollision异常,因为lookup中会根据”MilitaryShip”和”Asteroid”寻找对应的函数,而map中没有这样的函数。

    没有什么简单的办法解决这个问题,如果需要实现double-dispatching,而且需要支持  inheritance-based 参数转换,只能使用最早介绍的“双虚拟函数调用”机制。

    以上的代码中,collisionMap是静态的,一旦初始化便不再改动,如果需要动态处理,也就是能增加、删除、修改collisionMap中内容,则需要重新设计一个CollisionMap类:

    class CollisionMap {
    public:
        typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
        void addEntry(const string& type1, const string& type2, 
                    HitFunctionPtr collisionFunction, bool symmetric = true);
    
        void removeEntry(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& spaceStation);
    CollisionMap::theCollisionMap().addEntry("SpaceShip", "SpaceStation", &shipStation);
    
    void asteroidStation(GameObject& asteroid, GameObject& spaceStation);
    CollisionMap::theCollisionMap().addEntry("Asteroid", "SpaceStation", &asteroidStation);
    ...

    addEntry的symmetric参数,主要是为了能对称处理条目而设,也就是增加<T1,T2>时也会增加<T2,T1>。

    为了确保这些map条目在其对应的任何撞击发生之前就被加入map,可以使用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函数之前,就已经将所需的条目加入到了map中。

    稍后如果有新的派生类加入,无需改动原有代码,只需新增代码:

    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);
  • 相关阅读:
    利用HttpModule做流量记录
    VS2010 调试出现 asp.net development server 错误
    利用win7自带的虚拟WIFI网卡,与其他设备共享网络
    关于 ASP 中使用 Server.CreateObject("ADODB.Stream") 上传文件报错
    lightweight jobs
    YOLO v3 包括Tiny-Yolo 训练自己的数据集(Pytorch版本)以及模型评价指标的介绍
    Sublime Text3 下载安装与激活使用
    QT中自定义封装控件笔记
    19_7_25-7_27 暑假学校收获
    数字图像处理基础知识2
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/9625438.html
Copyright © 2011-2022 走看看