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

Started by
16 comments, last by Arcibald Wearlot 20 years ago
Use the typeid operator. Here is some info:

Grammar

postfix-expression:
typeid( type-id )
typeid( expression )

The typeid operator allows the type of an object to be determined at run time.

The result of typeid is a const type_info&. The value is a reference to a type_info object that represents either the type-id or the type of the expression, depending on which form of typeid is used. See type_info Class for more information.

The typeid operator does a run-time check when applied to an l-value of a polymorphic class type, where the true type of the object cannot be determined by the static information provided. Such cases are:

A reference to a class
A pointer, dereferenced with *
A subscripted pointer (i.e. [ ]). (Note that it is generally not safe to use a subscript with a pointer to a polymorphic type.)
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Advertisement
Using typeid() to determine type is just as bad as using dynamic_cast... it is generally considered bad practise to use either, because it does not fit in with the OO Paradigm.

Ideally, you would define a pure virtual function in the base, and implement the functions in all subclasses...
yes, your version IS the GoF version ... but that doesn''t mean it has no weaknesses ... sorry, I guess I should have said .. i want a better ALTERNATIVE to the visitor pattern ...

But as to the other person, you DO have to recompile your WHOLE source tree if you add a single line of code to your Visitor class, BECAUSE the visitor class must be #included by the base class ... or in the .cpp file for each and every derived class which implements Visit()

and forward declaration would not / could not work, because the Visit(Visitor &v) function MUST have the Visitor class fully included to be able to call v.Visit(MyType &obj);

so yes, it''s visitor, and from a design perspective it''s not too bad, cause all you have to know is that you want your object to be visited when you design the tree ... the whole point of visitor is that it provides a system that acts like a Virtual Function (type specific operation) matched up with a Callback Function (actual operation variable passed on supplied parameter) ... this is awesome ... but the original poster has no need for the callback aspect ... and has to pay the rebuild price inheirant in the visitor pattern ...

This weakness is an inheirant cost of the patterns mecahnism and the C++ compile time system ... obviously a langauge like SmallTalk or ruby would not have that issue at all.
psamty10 - it''s not about good or bad, it''s about the behavior it has and the behavior you want ... they either match or they don''t ...

NOW, for those who haven''t thought it through ...

the behavior of dynamic_cast<> is the same as the idea behind the COM query interface ... it tells you if the object you are providing is derived from the type you want ... ANYWHERE in it''s tree ... it is for doing stuff like ... if the passed in argument is ANY CLASS DERIVED FROM IMovingObject, then move it, else don''t ...

the behavior of typeid() is to provide the actual LEAF type of the object ... and is used to see if a passed in object is EXACTLY the type you are looking for ... or for debuging, just to print out a list of the types of polymorphic objects to see what kinda stuff you have ...

so comparing the typeid() of a passed in object, to that of a known type, is NOT like casting at all, it ONLY passes if the corresponding dynamic_cast<>() version would have succeeded with NO CHANGE in the type of the object ...

the THIRD way people solve this problem is with either virtual function(s) in the base class, of a member variable, set to the leaf type ... stuff like:

bool IsDerivedFrom(SOME_TYPE_IDENTIFIED_HOWEVER_YOU_WANT);

or

string TypeName(void);
int TypeId(void);

or

cosnt TypeDescriptor* GetTypeDescriptor(void)

where a TypeDescriptor has the above functions, like name / id, but ALSO has a function like:

const TypeDescriptor* GetParentTypeDescriptor(void)

so you can drill up the tree as needed.

quote:But as to the other person, you DO have to recompile your WHOLE source tree if you add a single line of code to your Visitor class

This is where you misunderstand visitor, because that's false. If you look at my code above you'll see that EntityVisitor itself is an abstract base-class. You only need to change EntityVisitor whenever you change the class-hierarchy it's designed to visit. Thus a change in a concrete visitor (see below) will not require any full rebuild.

Then any class can inherit from EntityVisitor and none of the Entities could care any less about what kind of visitor it is visited by. Thus you can have a PrintEntityVisitor like this (building on previous code):
class PrintEntityVisitor : public EntityVisitor{public:  virtual void Visit(CBaseEntity &be)  {    std::cout << "CBaseEntity";  }  virtual void Visit(CHumanEntity &he)  {    std::cout << "CHumanEntity";  }};

It's quite clear that the Entity-classes nor the EntityVisitor has any need to know about PrintEntityVisitor, it's also clear by your ideas about visitor that your experience with them is lacking.

Personally I frequently use visitor when I write compilers since that allows me decouple type-checking, code-generation, etc from the actual parse-tree.

[edited by - amag on March 20, 2004 11:21:31 AM]
sorry, let me clarify .. you are correct, and I did NOT misunderstand you ...

you must recompile when VISITOR changes ... the base class, which is when you add any more overloaded version of Visit, which is when you add any more classes to your heirarcy to be visited ... so you must recompile the whole tree to add any new special derived classes to it ...

but you are correct, you do NOT recompile when you add a new Visitor Derived class, which is basically a "function" ... examples of derived classes might be print or save to disk, or anything else delete if named "bob" ...

I called that part the (Callback Function) aspect of the visitor pattern, and yes, you can change and extend it at will ... but the (Virtual Function) side of it (special handling of derived classes) requires rebuilding the whole tree ... because it requires changing the Visitor class ... hence my statement.
There's always an acyclic visitor implementation. That ameliorates many of the coupling problems between classes in the traversed hierarchy and the visitor.

edit: spelling

[edited by - SiCrane on March 21, 2004 4:51:01 AM]
quote:Original post by Xai
psamty10 - it''s not about good or bad, it''s about the behavior it has and the behavior you want ... they either match or they don''t ...

the behavior of typeid() is to provide the actual LEAF type of the object ... and is used to see if a passed in object is EXACTLY the type you are looking for ... or for debuging, just to print out a list of the types of polymorphic objects to see what kinda stuff you have ...


Well, in this particular situation, consider the following:

class BaseObj {}
class Weapon : public BaseObj {}
class Gun : public Weapon {}

Now this:
if(typeid(Gun)==typeid(Weapon))
returns false, so if you use typeid, you need a branch for EVERY SINGLE class, not just the immediate ones. Its very likely that someone would forget to add a branch for every game object (especially since you could well have 30-40 derived classes of BaseObj), and this implementation makes the code look !!Blukk!!

As you said, dynamic casting casts to ANY base class matching the type specified, and is therefore not a good solution either -> but if you made sure the branches were in order (most specific -> least), that would work too.

IMHO, for this sort of problem you definitely want to use virtual functions, or amags visitor implementation.

This topic is closed to new replies.

Advertisement