C++中有没有类似OC中的isKindOfClass的东西??

还是OC好用,请问C++怎么实现类似的效果:isKindOfClass来查询是否属于某个类。
当然,子类也应该认为是一种基类。也就是说子类调用isKindOfClass(基类)也是成立的。

用dynamic_cast(var)进行转换,type是你的转换后的类型,var是你要转换的对象。以下是使用注意事项:

  1. 二者必须同时是指针或引用类型
  2. 当转换指针类型时,若转换成功,则得到type类型对象;若转换失败,返回nullptr。当转换引用类型时,若转换成功,则得到type类型对象;若转换失败,则抛出异常。
  3. dynamic_cast通常用于向下转型,即向子类转型(语言已经支持直接向上转型),必须注意,当你在做向下转型时,当前对象类型中必须含有虚函数,否则编译报错。
  4. 支持交叉转换,即当var类型并非继承于X,但实际类型继承于X,则dyanmic_cast(var)同样成功
  5. dynamic_cast开销相当大,尽量少用

因为语言关系,C++对RTTI(运行时类型识别)支持相当有限,因此尽量使用多态来达到目的,而非判断类型。
楼主的问题,可以封装一个isKindOf()的宏,如:
#define isKindOf(type, instance) (dynamic_cast(instance) != nullptr)
但强烈不推荐这样做,如果能够确定类型,推荐使用static_cast进行转换,开销非常小

其实我这个问题是来自Box2d里面b2Body对象的getUserData()方法,它返回的正好是个void*。

然而,我的代码中会有继承关系。比如,BigBoss、SmallBoss都派生自Boss基类。然后都会将它们setUserData(xxx)到b2Body上。

但是,getUserData()取到的指针的时候,就很难处理了,dynamic_cast没办法cast void*。

C++ 反射比较薄弱,不过可以通过dynamic_cast来判断

— Begin quote from ____

引用第2楼maxxfire于2016-01-12 12:36发表的 回 1楼(prompthu) 的帖子 :
其实我这个问题是来自Box2d里面b2Body对象的getUserData()方法,它返回的正好是个void*。

然而,我的代码中会有继承关系。比如,BigBoss、SmallBoss都派生自Boss基类。然后都会将它们setUserData(xxx)到b2Body上。

但是,getUserData()取到的指针的时候,就很难处理了,dynamic_cast没办法cast void*。 http://www.cocoachina.com/bbs/job.php?action=topost&tid=456050&pid=1478716

— End quote

如果是能接受足够多的编辑的,可以参考下现在2dx中读取studio导出文件生成UIWidget部分的代码。

楼主,你的问题应该属于框架问题,box2d是一个独立的框架,因此无法得知userdata的具体类型。要让box2d配合你的游戏,你必须自己实现box2d到你的游戏逻辑的衔接,一个比较普遍和简单的方法就是创建一个基类型,或者直接继承于Node或子类(不建议这样做,因为Node是用来渲染的,而不是逻辑),该基类型应当类似于java的接口,并在子类中实现,基类中设置getKind这样的抽象方法,这样在你的逻辑(主要是碰撞回调)就可以取得该基类型的对象,调用getKind辨识出具体的碰撞类型,但是这种方法有一个特点,必须始终记住只要派生出一个子类,就必须实现getKind方法,否则就会调用父类的getKind方法。

此外,有一点要牢记,在碰撞回调中得到的对象是抽象类型,通过getKind,您已经可以确定他的具体类型,这时,如果您需要类型转换得到子类型对象,请务必使用dynamic_cast,另外在设置userdata的时候使用static_cast转换到抽象基类再进行设置;还有一种更快速的方法,即用大括号强制转换过去(无任何CPU开销),不过这种方法是原始的C语言类型转换,不包含任何类型内部处理(主要是虚函数表的处理),所以当采用此方法时,请确保您的抽象基类要作为子类的第一继承位,setuserdata时,可以直接设置,而无需static_cast转换。当然,在C++中我们也不鼓励第二种方法,当出现其他需求要求我们的第一继承位必须是某个类的时候,这种方法就不能用了,比如使用成员函数指针时的类继承关系设计就需要特别谨慎

不错,用统一基类就可以避免dynamic_cast void*指针的问题,那就可以使用dynamic_cast来解决这个问题了。
不过你说的getKind又是一种什么方式呢,getKind应该返回什么呢,不大明白如何使用?

这么说吧,你从userdata得到基类对象,但由于所有碰撞对象都是统一基类的,你仍然不知道具体是什么类型,从而转换过去,所以在该基类中设计getKind这样的方法,就可以知道具体类型,从而使用dynamic_cast转换过去,例如:

class Collider {
public:
    virtual ~Collider() {}
    virtual string getKind() const = 0;
};

class Fruit : public Collider {
private:
    b2Body *m_body;
public:
    string getKind() const override {
        return "fruit";
    }    
    void createBody(b2World &world) {
        ... // 创建body
        m_body->SetUserData(static_cast(this)); // 如果Collider不是第一继承位,则static_cast是必要的,这里可以省略
    }
};

class Monkey : public Collider {
private:
    b2Body *m_body;
public:
    string getKind() const override {
        return "monkey";
    }
    void createBody(b2World &world) {
        ... // 创建body
        m_body->SetUserData(static_cast(this)); // 如果Collider不是第一继承位,则static_cast是必要的,这里可以省略
    }
    void eat(Fruit *fruit) {
        ...
    }
};

void MyContactListener::BeginContact(b2Contact* contact) {
    auto colliderA = (Collider*)contact->GetFixtureA()->GetBody()->GetUserData();
    auto colliderB = (Collider*)contact->GetFixtureB()->GetBody()->GetUserData();
    Monkey *monkey = nullptr;
    Fruit *fruit = nullptr;
    if(colliderA->getKind() == "monkey" && colliderB->getKind() == "fruit") {
        monkey = dynamic_cast(colliderA);
        fruit = dynamic_cast(colliderB);
    } 
    else if(colliderB->getKind() == "monkey" && colliderA->getKind() == "fruit") {
        monkey = dynamic_cast(colliderB);
        fruit = dynamic_cast(colliderA);
    } 
    if(monkey != nullptr && fruit != nullptr) {
        monkey->eat(fruit);
        game->delayDestroy(fruit); // 不可以在回调中删除body,所以必须延迟删除
    }
    ...
}

```


通常,在碰撞回调中写逻辑会使得该回调非常臃肿,所以,在Collider类中可以设计onCollideBegin和onCollideEnd虚函数,则Monkey和Fruit类的onCollideBegin可能长这个样子:
void Monkey::onCollideBegin(Collider *collider, Contact *contact) {
    if(collider->getKind() == "fruit") {
        eat(dynamic_cast(collider)); // 这里eat方法可以设为private访问,加强封装
    }
    else if(collider->getKind() == "coin") {
        ... 
    }
    ...
}

void Fruit::onCollideBegin(Collider *collider, Contact *contact) {
    if(collider->getKind() == "monkey") {
       game->delayDestroy(this); // 不可以在回调中删除body,所以必须延迟删除
    }
    else if(...) {
        ... 
    }
    ...
}

// ContactListener中的BeginContact回调可能是这样
void MyContactListener::BeginContact(b2Contact* contact) {
    auto colliderA = (Collider*)contact->GetFixtureA()->GetBody()->GetUserData();
    auto colliderB = (Collider*)contact->GetFixtureB()->GetBody()->GetUserData();
    colliderA->onCollideBegin(colliderB, contact);
    colliderB->onCollideBegin(colliderA, contact);
}

```

明白了,getKind就是相当于OC中的isKindOf,感谢感谢~