Archived

This topic is now archived and is closed to further replies.

Arcibald Wearlot

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

Recommended Posts

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]

Share this post


Link to post
Share on other sites
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;
}

Share this post


Link to post
Share on other sites
Guest Anonymous Poster

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.

Share this post


Link to post
Share on other sites
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);
};
// etc

class EntityVisitor
{
public:
virtual ~EntityVisitor() {}
virtual void Visit(CBaseEntity &be) = 0;
virtual void Visit(CHumanEntity &he) = 0;
// etc

};
// in cpp-file

void 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]

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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)....

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.)

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites