how to know the exact type of an object when using class inheritance

Started by
16 comments, last by Arcibald Wearlot 20 years, 1 month ago
i use derived classes to handle entities in my game: i created a CBaseEntity class, and derived other classes from it, such as CHumanEntity. i stored all the entities like this:

std::vector<CBaseEntity*> m_Entities;
this way i can fit all the different entities into a single array, making drawing and managing easier. but sometimes i want to know the exact class of one of them: for example if the entity #3 is a CHumanEntity or a CVehicleEntity.. is there a simple way to do this? [edited by - Arcibald Wearlot on March 19, 2004 6:23:03 AM] [edited by - Arcibald Wearlot on March 19, 2004 6:23:15 AM]
Advertisement
You can dynamic cast it to a type to test and it will return true if it is of the type u casted to.

A better way would be just to make the base class have a int which is initialised by each and every constructor of any derived class.

#define Base 0
#define Human 1
#define Building 2

Human()
{
type = Human;
}

Building()
{
type = Building;
}
CBaseEntity* foo = ...;if (CHumanEntity* x = dynamic_cast<CHumanEntity*>(foo)) {  //use x, the human entity} else if (CVehicleEntity* x = dynamic_cast<CVehicleEntity*>(foo)) {  //use x, the vehicle entity} 


But the preferred approach is to use virtual functions of course.
Why do you need to know the exact type?
In most cases you can use the visitor-pattern instead of a dynamic_cast. For instance what the AP is suggesting would be better served with a visitor-pattern.
Some code:
class EntityVisitor;class CBaseEntity{public:  virtual void Accept(EntityVisitor &ev);};class CHumanEntity : public CBaseEntity{public:  virtual void Accept(EntityVisitor &ev);};// etcclass EntityVisitor{public:  virtual ~EntityVisitor() {}  virtual void Visit(CBaseEntity &be) = 0;  virtual void Visit(CHumanEntity &he) = 0;  // etc};// in cpp-filevoid CBaseEntity::Accept(EntityVisitor &ev){  ev.Visit(*this);}void CHumanEntity::Accept(EntityVisitor &ev){  ev.Visit(*this);}

Then you inherit EntityVisitor to do special stuff depending on the real type of your entity.

[edited by - amag on March 19, 2004 9:07:39 AM]
amag is right

Also, if you need a list of humans have a list of CHumanEntity* as well.

If you''re making decisions based upon type anywhere other than object creation you should use virtual functions or group according to type.

Does that make sense?
quote:Original post by GamerSg
You can dynamic cast it to a type to test and it will return true if it is of the type u casted to.

A better way would be just to make the base class have a int which is initialised by each and every constructor of any derived class.

#define Base 0
#define Human 1
#define Building 2

Human()
{
type = Human;
}

Building()
{
type = Building;
}


Actually I believe dynamic_cast IS the preferred method (over the int type). IMO, the base class would ideally be unaware of the types of classes that can inherit it. Moroever, this gets even trickier when you use multiple inheritance in the derived class.

The tradeoff for the visitor technique is that you will not have access to private members of the derived class (as opposed to virtual functions, which have complete access as they are members).
Also, for every class you create, you would need to add a function in the visitor class, which to me violates the OO nature of the program...

Ideally youd use virtual functions, to me the visitor solution is a little lazier than VFs, but better than the others (dynamic_cast or adding an int)....
sorry amag, but your solution doesn''t make sense ... I mean I understand it''s functions .. but you have the highest an EntityVisitor which knows about ALL special derived types ... and the Base class of the types knows about an EntityVistor .. so indirectly about it''s derived types ... this means any adition requires the recompilation of the entire tree ...

an ideal system would NOT require the base class to be aware of an object which is aware of the derived classes, by name ...

now your idea does serve a usefull purpose, but it''s just not "prefered" in my book ..

dynamic cast has it''s own down sides, which are: A) it does NOT tell you what LEAF an item is, so it is unsuitable for getting the name or some other situation of a type (btw THIS type of use is almost always best implemented as a virtual function) ... and B) the if/elses are fragile, require updating often, AND are order sensitive (put the refactor your inheiritance tree, and all the checks that have been implemented are now potentially incorrect - cause tests MUST be done leaf to base for any algorithm that is looking for "most derived" status)

I DO like the visitor pattern, i just think your implementation of it needs improvement ...

The default (nonaware) visitor pattern, should only invoke virtual functions, and hence doesn''t have these problems - but doesn''t have a lot of power outside of what was thought of originally by the base class designer. A special cased visitor would could dynamic_cast to do special (unplanned by class writter) handling. And Amag''s visitor (one which requires being designed into the base class from the beginning, has the power of the dynamic_cast version, with a cleaner syntax - BUT cannot be used to extern functionality of existing systems - and has the previously mentioned built problem) ...

A COM-like QueryInterface system is basically the same as a dynamic_cast system ... with the minor improvement that the interfaces are "named" and not expressed in C++ code types (you can''t have a file specify a C++ type "std::string" and have it automagically know what that is .. but if you add a registry of known types/interfaces ... then you can have a lookup map to allow such things ... (aka the registry in COM) ..

I''ve gotten off topic .. oh well ..

I want a better visitor dammit!
The entire tree would not need to be recompiled... thats the whole point of the visitor pattern.

The only class that needs to be recompiled is the visitor class itself...the base class basically need not know anything about any other class - simply use the following
virtual void Accept(EntityVisitor &ev) = 0; // in the base

I actually think hes using the standard implementation...
Sorry Xai, it's you who don't make sense (sorry for the pun). It seems you don't know what the visitor pattern is. My example is the definition of visitor (according to "Design Patterns" by GoF, read it if you get the time). I'd like to see how you would implement visitor in a "better" way.
quote:The default (nonaware) visitor pattern, should only invoke virtual functions, and hence doesn't have these problems

So how exactly does "only invoking virtual functions" free you from the requirement of knowing about the types?

The purpose of visitor is to allow one to extend the interface of an existing class-hierarchy without changing that hierarchy (or the base of it).
The purpose of virtual methods is to allow a (derived) class to change the implementation of an interface.
They do not solve the same problem.

Visitor is also usable when you want to do something based on the class' type, but you don't feel that this something really is a responsibility of that class.

[edited by - amag on March 19, 2004 7:39:22 PM]
another loosier way is to do

class CVehicle{    virtual class CCar*   GetCar  () { return NULL; }    virtual class CTruck* GetTruck() { return NULL; }    virtual class CBuss*  GetBus  () { return NULL; }    virtual class CBike*  GetBike () { return NULL; }};class CCar: public CVehicle{    virtual CCar*   GetCar  () { return this; }};class CBike: public CVehicle{    virtual CBike*   GetBike() { return this; }};// ect...[source]but having type identifiers is always useful (for saving and other stuff). It''s also good to store the identifiers in a common list. 

Everything is better with Metal.

This topic is closed to new replies.

Advertisement