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

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:

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)....

0

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!

0

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 followingvirtual void Accept(EntityVisitor &ev) = 0; // in the base

I actually think hes using the standard implementation...

0

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.

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

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:

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):

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

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 ...

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.