C++:识别对象的类型

C++: Identifying the type of the object

本文关键字:类型 对象 识别 C++      更新时间:2023-10-16

我很好奇当前C++和C++11中可用的结构或语言功能可以用来推断对象的类型。一个例子:

class Base {
};
class DerivA
    : public Base {
};
class DerivB
    : public Base {
};
void foo(Base* obj) {
    // Identify if `obj` is a `DerivA` or a `DerivB`
}

这过于简单化了。看起来,与其有一种方法来识别类型,最好的解决方案是为两个派生类型重载函数,并去掉基类。

我真正的用例是一个类对对象的确切类型不感兴趣(即只需要Base的实现),而另一个类需要确切地知道第一个类使用的是Base的实现。

这种情况发生在基于组件的游戏实体系统中。基础为EntityState,其派生类型为StandingStateDeadState等。类Entity只需要一个通用的EntityState对象,类EntityRepresentation需要确切地知道实体处于什么状态,以决定是绘制"站立"动画还是绘制"死亡"动画,或其他什么。


编辑:当然,如果可能的话,我希望以这样一种方式实现游戏,即使实体表示也不需要知道实体状态的类型。如果有办法做到这一点,那么我会使用它。:)我会研究访问者模式。

您可以使用dynamic_cast

if(DerivA * derivA = dynamic_cast<DerivA*>(obj)){
    // it is a DerivA
}

两种方式:

如果你的类是多态使用,dynamic_cast

或者您可以使用typeid

typeid的用法

#include <typeinfo.h>

typeid(YourClass).name()

dynamic_cast的使用

DerivA& dynamic_cast<DerivA&> (object);
DerivA* dynamic_cast<DerivA*> (object);

基类中必须至少有一个虚拟函数才能使dynamic_cast工作,否则将出现编译错误。

如果试图强制转换指向一个不是实际对象类型的类型的指针,则强制转换的结果将为NULL。对于引用的类似情况,强制转换将抛出bad_cast异常。

查看标准库的<typeinfo>部分(例如,请参阅此处)

您可以使用dynamic_cast来标识派生类对象的类型。例如,当执行:DerivedA* p = dynamic_cast<Derived*>(pBase);时,如果满足p!=NULL条件,则它是DerivedA类型的对象。

您在上一段中描述的问题可以使用访问者模式来解决。你试过了吗?它甚至可以在不知道其操作类型的情况下解决问题。

与大多数建议相反,我不会直接使用RTTI(typeinfo或dynamic_cast)。你可以做不同的事情:

  1. 添加提供绘制所需信息的函数
  2. 使用双重调度机制

最简单的解决方案可能是1),只需添加一个虚拟方法,告诉你对象处于什么状态,并使用它来确定如何设置对象的动画。这种方法的问题是,它需要为每一个需要它的东西向State类添加方法:动画、声音、运动计算。。。

使用类似访问者模式的双重调度形式将复杂性从State层次结构转移到访问者层次结构中,访问者层次结构必须包含每个不同State(在所有级别)的重载。应用程序中的模型将更简单,但该模型的使用将变得更加繁琐。

假设你的类型中有一个虚拟方法,你可以使用C++的实时类型识别(RTTI),比如dynamic_cast和typeid

然而,更好的设计可能是实现虚拟方法来完全隐藏类型。例如:

class EntityState {
  virtual void Draw( Entity entity ) = 0;
  }
class DeadState : EntityState {
  virtual void Draw( Entity entity ) {
    //*** render the entity as dead
    }
  }
class AliveState : EntityState {
  virtual void Draw( Entity entity ) {
    //*** render the entity as alive!
    }
  }
class Entity {
  EntityState myEntityState;
  void Draw() {
    myEntityState.Draw( this );
    }
  }

现在,您的实体可以被绘制为死或活,而不需要任何if-then-else或switch语句代码,如果您突然希望将新状态添加到实体中,则需要更新这些代码。

通常比查询对象类型更好的选择是向基类中添加一个虚拟函数:

class Base { public: virtual int Animation() const=0; };
class DerivA : public Base { public: int Animation() const { return 0; } };
class DerivB : public Base { public: int Animation() const { return 1; } };

然后用一个整数标识所有不同的动画,可能有一个不可修改的动画数组:

Animation anim1, anim2, anim3;
Animation *array[5] = { &anim1, &anim2, &anim3 };
void foo(Base *b) {
   int animnum = b->Animation();
   Animation *anim = array[animnum];
   ...
 }

这至少是让它正常工作的一种方法。

对于继承性,我将研究static_cast和dynamic_cast。您可以使用这些来确定对象是否是从类继承的。