Sign in to follow this  
johnnyBravo

When casting a class over a base class, anyway to tell if it is the right class?

Recommended Posts

johnnyBravo    100
Hi i'm using c++, and I've got a Base class and then I have the Draw, Input, Sound and many more classes inheirting off it. If I were to have a situation like so below: Base * b = someInstanceOfBaseOrDrawOrInputOrEtcClass; How would I check if the pointer is pointing to a Draw class instance and not some other? eg I would do a check if it is a Draw class first before doing this below, if it isn't I would just call an exception: ((Draw)b).someDrawFunc(); Thanks,

Share this post


Link to post
Share on other sites
SiCrane    11839
If your base class has virtual functions (and it should at least have a virtual destructor) you can use a dynamic_cast.

Share this post


Link to post
Share on other sites
dotproduct    180
Quote:
Original post by SiCrane
If your base class has virtual functions (and it should at least have a virtual destructor) you can use a dynamic_cast.


And check if the result is a valid pointer (i.e. != NULL)

Share this post


Link to post
Share on other sites
LordShade    251
There shouldn't be any reason to do what you are asking. You should examine your design. If you want to call the base classes implementation of a function you can use: CDraw::Draw(...)

If you're storing "Draw-able" objects and ones that can't in the same list, split the list.

Happy Coding.

Share this post


Link to post
Share on other sites
rip-off    10979
Quote:
Original post by johnnyBravo
Hi i'm using c++, and I've got a Base class and then I have the Draw, Input, Sound and many more classes inheirting off it.

If I were to have a situation like so below:
Base * b = someInstanceOfBaseOrDrawOrInputOrEtcClass;

How would I check if the pointer is pointing to a Draw class instance and not some other?

eg I would do a check if it is a Draw class first before doing this below, if it isn't I would just call an exception:
((Draw)b).someDrawFunc();

Thanks,


ummm, what do Input, Draw and Sound (and many more) have in common?

if you have to know the types of all the individual classes, then the Base class probably isnt going to work out.

im just wondering as to why you've decided to do it this way.

Share this post


Link to post
Share on other sites
johnnyBravo    100
thanks.

Quote:
Original post by rip-off
Quote:
Original post by johnnyBravo
Hi i'm using c++, and I've got a Base class and then I have the Draw, Input, Sound and many more classes inheirting off it.

If I were to have a situation like so below:
Base * b = someInstanceOfBaseOrDrawOrInputOrEtcClass;

How would I check if the pointer is pointing to a Draw class instance and not some other?

eg I would do a check if it is a Draw class first before doing this below, if it isn't I would just call an exception:
((Draw)b).someDrawFunc();

Thanks,


ummm, what do Input, Draw and Sound (and many more) have in common?

if you have to know the types of all the individual classes, then the Base class probably isnt going to work out.

im just wondering as to why you've decided to do it this way.


I was thinking of making each sub system of the program eg, sound, input etc into components that could be attached to the Main class, so if i didn't need sound, I could just not attach it to the Main class.

edit:
then for the input class you could add the components keyboard, mouse and/or joystick

and also for the render/draw class you could add components like ParticleSystem, MeshSystem etc

..im trying to find a good way to make my code more reusable, etc

Share this post


Link to post
Share on other sites
Glak    315
There are only two times that you need to cast:

1) Your design is wrong
2) The programming language is missing a feature

C++ has a lot of features so chances are it is the first case.

Share this post


Link to post
Share on other sites
johnnyBravo    100
Quote:
Original post by Glak
There are only two times that you need to cast:

1) Your design is wrong
2) The programming language is missing a feature

C++ has a lot of features so chances are it is the first case.


Yes I am indeed most probably doing something wrong ;)

Share this post


Link to post
Share on other sites
hplus0603    11356
That's fine. Typically, you derive everything from class Interface (your Base), and implement the function getInterface() on Interface:


class Interface {
public:
virtual void addRef() = 0;
virtual void release() = 0;
virtual bool getInterface( char const * name, Interface ** oInterface ) = 0;

template< class T >
bool getInterface( T ** oT ) {
Interface * i;
bool ret = getInterface( typeid(T).name(), &i );
if( ret ) {
*oT = static_cast< T * >( i );
}
return ret;
}
};


The implementation for getInterface() in "Sound" would look something like:


bool Sound::getInterface( char const * name, Interface ** oInterface ) {
if( !strcmp( name, typeid(Sound).name() ) ) {
*oInterface = this;
addRef();
return true;
}
return false;
}


You can then use containment to attach functionality to the main program:


class Main : public Interface {
public:
void setInterface( char const * name, Interface * i );

template< class T >
void setInterface( T * t ) {
setInterface( typeid( T ).name(), i );
}
...
private:
std::map< std::string, IfPtr > ifs_;
};

bool Main::setInterface( char const * name, Interface * i ) {
ifs_[name] = i; // let IfPtr do its job
}

bool Main::getInterface( char const * name, Interface ** oInterface ) {
std::map< std::string, IfPtr >::iterator i = ifs_.find( name );
if( i != ifs_.end() ) {
*oInterface = (*i).second;
(*oInterface)->addRef();
return true;
}
return false;
}


Thus, you could create arbitrary components, and add them to the Main class, and then retrieve them back by interface name (if present). Using the Typeid versions makes the system (mostly) type safe -- if you put in a Sound interface pointer, you'll get back that pointer, static-cast to the right type. The static_cast<> makes sure that you have the right headers included to do the down-cast safely.

This is somewhat similar to how COM does dynamic type discovery, although COM also uses QueryInterface() for manufacturing new instances, which I think is the job of a specific factory function, rather than just QueryInterface. I e, I think your Sound interface should have a makeSoundInstance() member function which returned sound instances, rather than implementing that through overloading getInterface().

Also, for Glak: casts are sometimes useful -- such as in this case. There's no particular feature missing from C++ (unless you count truly dynamic dispatch), but this is pretty well-recognized as a case where down-casting makes sense.

Share this post


Link to post
Share on other sites
Glak    315
using typeid stuff is pretty much the same as casting, and indicates a problem. You should not need to get derived class information from something assigned to a base pointer. If you want to access Sound or whatever just make it global, either a global variable, global namespace, or global singleton. Some sort of global.

A lot of people come in here saying things like, I have B and D derived from A, I have a list of A and want to do a() for each A, b() for each B and c() for each C. They come up with the solution to do some sort of typeid thing through the loop, but really what they need are three lists. The same object can be in multiple lists, manipulated by the interface that it presents in each list.

Share this post


Link to post
Share on other sites
dotproduct    180
Quote:
Original post by Glak
using typeid stuff is pretty much the same as casting, and indicates a problem. You should not need to get derived class information from something assigned to a base pointer. If you want to access Sound or whatever just make it global, either a global variable, global namespace, or global singleton. Some sort of global.


Eh... So you're saying you should use globals instead of casting?

Share this post


Link to post
Share on other sites
MaulingMonkey    1730
Quote:
Original post by dotproduct
Quote:
Original post by Glak
using typeid stuff is pretty much the same as casting, and indicates a problem. You should not need to get derived class information from something assigned to a base pointer. If you want to access Sound or whatever just make it global, either a global variable, global namespace, or global singleton. Some sort of global.


Eh... So you're saying you should use globals instead of casting?


Sounds like bad advice to me. Partially, anyways.

typeid/dynamic_cast can be the smell of a design problem - that of too coarsely grained dat. You've got something which you don't know the type of, and by your solution's nature, it either should (e.g. change from "Base * b" to "Derived1 * d" - or mantain seperate containers of input and draw, instead of 1 of pointers all to base) or shouldn't (e.g. from "if ( derived1 ) b->do_something_derived1() else if ( derived2 ) ..." to "b->do_something()" which is a virtual which calls do_something_derived1/derived2/etc - or throws if it dosn't implement it).

That said, the solution is sometimes more complex than simply mantaining somewhat brittle code as illustrated above.

Just 2¢

Share this post


Link to post
Share on other sites
hplus0603    11356
Quote:
using typeid stuff is pretty much the same as casting, and indicates a problem.


Note that you don't have to use typeid() in the implementation at all -- I just used it because it's an easy source of strings to use as interface identifiers. You could use names of football teams, or integers, if you want -- as long as you get a unique identifier per interface you may wish to query for.

A second note is that the use I'm making of typeid() is _not_ analogous to dynamic_cast<>, because it is resolved statically at compile time -- it'll work even if you turn off RTTI. Additionally, getInterface() lets you decide what specific classes to let others find pointers to, whereas dynamic_cast<> doesn't let you totally decide -- any public base of a public base will be accessible. Further, dynamic_cast<> doesn't support delegation, whereas getInterface() does. Last, dynamic_cast<> is very, very slow -- the strcmp() implementation of getInterface() I showed is much faster, and a version that uses interned strings or interface registries would be faster still.

A third note is that you CAN make a public accessor for any interface you may wish to get on any interface you may wish to get it from. However, that quickly becomes way too wide, and the getInterface() solution is actually more maintainable and easier to work with. Typical examples come when sorting out entities inside notifications -- i e, entity A collided with entity B, and wants to know whether entity B supports some special kind of resolution. You should NOT make it a rule that every entity needs to support the special resolution rules that entity A wants, nor even an accessor for those rules, or adding/improving your entity behavior system becomes ridiculously unweildy.

In fact, getInterface() allows you to de-couple your logical design from your physical design, which is a major benefit in many game programming situations. I you're going to say it's a bad design tool, then you're going to have to put a LOT more weight behind that statement, with specific examples of what better solution you have to the same problems. ("use a global" isn't, and "make Entity support abstract accessors for anything you might ever want" also isn't)

Share this post


Link to post
Share on other sites
LordShade    251
If you're going to go the way of the Type ID, why not take the plunge and go full-COM based objects? It has support for singletons and interfaces and with ATL is fairly easy to implement. Sure if you don't have any exposure to implementing COM objects, you've got some reading to do. Really though, COM is a great tool that is underutilized.

Again, if you're base class is asking the type of it's parent, you've got a 'cludge' and need to examine your design.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this