PDA

View Full Version : C++ Question - isOfType(T *obj, const string &s)


Jesse Aldridge
08-08-2005, 12:07 PM
class BaseObject
{
virtual ~BaseObject(){}
};

class Shootable
{
virtual ~Shootable(){}
};

class Shooter
{
virtual ~Shooter(){}

void handleShooting()
{
}
};


class Person : Shootable, Shooter, BaseObject
{
};

class Monster : Shootable, BaseObject
{
};


void handleObject( BaseObject *baseObj )
{
if( isOfType( baseObj, "Shooter" ) )

baseObj->handleShooting();
}

void main()
{
BaseObject *personObj = new Person;
BaseObject *monsterObj = new Monster;

handleObject(personObj);
handleObject(monsterObj);
}


Kind of weird code I guess, but it's just an example. Does anyone know of an easy way to set things up so I can get a function such as isOfType working as shown?

I've got it to work by using systems involving virtual functions and stuff, but thus far I haven't found any nice clean system for handling this sort of thing. I was wondering if anybody had any ideas.

I'm thinking of parsing all the .h files and making a class heirarchy file - but that will get tricky.

Rainer Deyke
08-08-2005, 12:17 PM
The standard way for checking types is dynamic_cast:
void handleObject( BaseObject *baseObj )
{
if (Shooter *shooterObj = dynamic_cast<Shooter *>(baseObj)) {
shooterObj->handleShooting();
}
}

Of course this only works if you know the type you want to check at compile time.

ggambett
08-08-2005, 12:22 PM
Rainer's solution is "the right one". However I also read somewhere that in OOP if you need to know the class of an object, your design is wrong. I'm not blaming you, I do use dynamic_cast, just make sure you need it.

dynamic_cast<type*>() is standard, returns NULL if it can't do the cast or a pointer if it can. As Rainer said, there's no standard way that I know of to do things like classOf(pPtr1) == classOf(pPtr2) or classOf(pPtr1) == pClass1. Probably there are compiler-specific ways to do that, though.

Jesse Aldridge
08-08-2005, 01:21 PM
Actually dynamic_cast doesn't seem to work in this case. Here's an example that actually compiles.
#include <iostream>

using namespace std;

class BaseObject
{
public:
virtual ~BaseObject(){}
};

class Shootable
{
public:
virtual ~Shootable(){}
};

class Shooter
{
public:
virtual ~Shooter(){}

void handleShooting()
{
}
};


class Person : Shootable, Shooter, public BaseObject
{
};

class Monster : Shootable, public BaseObject
{
};


void handleObject( BaseObject *baseObj )
{
//if( isOfType( baseObj, "Shooter" ) )
if( Shooter *shooterObj = dynamic_cast<Shooter*>(baseObj) )
{
cout<<"is a shooter"<<endl;
shooterObj->handleShooting();
}
}

int main( int argc, char* argv[])
{
BaseObject *personObj = new Person;
BaseObject *monsterObj = new Monster;

cout<<"Handling person"<<endl;
handleObject(personObj);
cout<<"Handling monster"<<endl;
handleObject(monsterObj);

delete personObj;
delete monsterObj;

system("PAUSE");
return 0;
}

I understand there's no standard way of handling this, I was wondering if anyone knew of any non-standard ways. I hadn't thought about compiler specific solutions, I'm using gcc through Dev-C++, anyone know of a compiler -specific solution for this?


Edit: Forgot to delete objects in code sample.

Martoon
08-08-2005, 01:55 PM
Personally, things like dynamic_cast<> and compiler-specific options give me the heebee-geebees(sp?).

When I want runtime type information, I usually build it into the functionality of the base class. For example, in your BaseObject, you would add a virtual isOfType() method which you override in each of the derived classes to return the appropriate value.

milieu
08-08-2005, 02:14 PM
Why do you need to call isOfType anyway? Why not override HandleObject in each class individually?


class BaseObject
{
public:
virtual ~BaseObject(){}
void handleObject( )
{
//default implementation does nothing
};
};

class Shooter
{
public:
virtual ~Shooter(){}

void handleObject()
{
//shooting code goes here
}
};


class Person : Shootable, Shooter, public BaseObject
{
void handleObject()
{
//call base classes for their implementations
Shootable::handleObject();
Shooter::handleObject();
BaseObject::handleObject();

//special person handling code goes here
}
};

class Monster : Shootable, public BaseObject
{
};


You could probably do this in far more clever ways, like using the Decorator pattern, but this would be a pretty simple approach. This allows you to put all of your items in a collection, and handle them en masse:


std::vector<BaseObject*> objectList;
objectList.push_back (new Person);
objectList.push_back (new Monster);

std::vector<BaseObject*>::iterator objectListIterator;
for (objectListIterator = objectList.begin();
objectListIterator != objectList.end();
objectListIterator ++)
{
BaseObject* pObject = *iter;
pObject->handleObject();
}

ErikH2000
08-08-2005, 02:15 PM
Actually dynamic_cast doesn't seem to work in this case. Here's an example that actually compiles.
Make sure you've got run-time type information being generated in your build.

For myself, I usually store a class identifier in a member variable if I really need something like that. But ideally, you can handle tasks that differ between objects by declaring virtual functions in your base class and overriding them as needed in child classes.

-Erik

digriz
08-08-2005, 02:44 PM
RTTI will work perfectly for what you want; but beware using it a lot in your code. Excessive use can cause performance hits.

Jesse Aldridge
08-08-2005, 05:00 PM
When I want runtime type information, I usually build it into the functionality of the base class. For example, in your BaseObject, you would add a virtual isOfType() method which you override in each of the derived classes to return the appropriate value.
This is exactly what I currently have implemented. However, I think it's cumbersome, and produces some hard to find errors - I'm hoping to find a better way.
Why do you need to call isOfType anyway?
Here are a couple examples of it's use in actual code:

void Item::getDropped()
{
looseSprite.setCenter(holder->getCenter().x + randRange(4),
holder->getCenter().y + randRange(4));

objMan.newObject(this);
holder = NULL;
if(isOfType(this, "Persistant"))
dynamic_cast<Persistant*>(this)->setLevel(getCurrentLevel());
}

///////////////////////////////

void Person::wieldMostPowerfulGun()
{
vector<HandGun*> guns;
int i;
for(i = 0; i < inventory.size(); i++)
if( isOfType(inventory.at(i), "HandGun") )
guns.push_back(dynamic_cast<HandGun*>(inventory.at(i)));

HandGun *bestGun = NULL;
for(i = 0; i < guns.size(); i++)
if(!bestGun || getRank(guns.at(i)) > getRank(bestGun))
bestGun = guns.at(i);

if(bestGun)
equip(bestGun);
}

Make sure you've got run-time type information being generated in your build.
I'm pretty sure it's on by default with Dev-C++. I know you have to turn it on with Visual Studio. Try running my example, I think even with RTTI, the dynamic_cast will fail. Just to make sure I had RTTI going, I tried this (which works fine):

BaseObject *p = new Person;
if(dynamic_cast<Person*>(p))
cout<<"successful cast"<<endl;
delete p;


RTTI will work perfectly for what you want
I'm not sure it will. I think the reason the dynamic_cast fails in my example is because Shooter and BaseObject are not directly related. They are both parents of Person. Any given BaseObject will not necessarily be a Shooter.

ErikH2000
08-08-2005, 05:20 PM
This is exactly what I currently have implemented. However, I think it's cumbersome, and produces some hard to find errors - I'm hoping to find a better way.
Yeah, I hear you. It's something I've bumped against more than once. There's just limitations to what you can do with RTTI information in C++. At the assembly-language level you could get at the RTTI more directly, but... naw, too much work.

A few more ideas:

* In your base class, make a GetClassID() or GetClassName() function that is pure virtual. This will at least "remind" you to override every child class and prevent some errors. Sorry, if that is too obvious. :)

* Write a pre-processor utility that parses your sources looking for classes and adds a class ID/name to each one. You could make it so that it runs automatically whenever you build. Basically, you are making your own RTTI this way and have more flexibility.

-Erik

gcarlton
08-08-2005, 06:03 PM
As an alternative, you can query the base object to find out its attributes. Consider this:

class BaseObject
{
virtual Persistant* getPersistant() { return NULL; }
...
};

Then either have it as a subobject:

class Shooter : public BaseObject
{
virtual Persistant* getPersistant() { return &myObject; }
Persistant myObject;
};

Or as an interface:

class Shooter : public BaseObject, public Persistant
{
virtual Persistant* getPersistant() { return this; }
};

Lastly, if you are sure the object should always be the correct type, it is inefficient to do a dynamic cast. You are better wrapping it in a function that does an assert (e.g. using dynamic cast), but in release just does the normal static cast. This comes up quite a bit if you are converting say between Person and a BaseObject proxy or something similar.

Dominique Biesmans
08-08-2005, 11:42 PM
Note that apart from dynamic_cast, there is the operator typeid, which allways returns the exact type of the object (you can't check for a base type, like with dynamic_cast).

also, typeid returns a const type_info&, which has .name() as a member, which returns a text representation of the type. Handy e.g. when you're serializing objects.

Personally, things like dynamic_cast<> and compiler-specific options give me the heebee-geebees(sp?).


Both dynamic_cast and typeid are standard C++ feautures, no reason to get all heebee-geebeed!

digriz
08-09-2005, 01:40 AM
Note that apart from dynamic_cast, there is the operator typeid, which allways returns the exact type of the object (you can't check for a base type, like with dynamic_cast).

also, typeid returns a const type_info&, which has .name() as a member, which returns a text representation of the type. Handy e.g. when you're serializing objects.

Both dynamic_cast and typeid are standard C++ feautures, no reason to get all heebee-geebeed!

Beat me to the post. :)

Jesse Aldridge
08-09-2005, 02:38 PM
Alright, thanks for all the posts. I think I should be able to get something working now.