base/child classes

Started by
10 comments, last by mago 22 years, 10 months ago
hey everybody I have this small problem here and I would really appreciate if somebody could give me a hint: I have a base class for object, lets call it CObject . This class contains it's ID, description and pointer to list of the object's attributes. Then there are its children - classes like CWeapon, CArmor, etc. These don't add anything else but a constructor that initializes the variables in CObject to proper values and functions for easier access to these variables. Then I have a storage class, a list of pointers. This is a template class, therefore it can operate on any type of pointers(CObject*, CWeapon*, CArmor*). This class should also be responsible for creating objects of those classes by calling CList::Add(new T(parameters)). Now this is the problem: I need all the children of CObject to be stored in the same list, so the list only operates on them as if they were CObject, not CArmor or CWeapon. But the constructors of CWeapon and CArmor take different parameters... How can I make it possible for the list to operate on every child class separately when they are added, but still add them to the same list so when they are retrieved, they are all of the same type(CObject)? Anyone? Oh and I should also mention that I need this to work with any number of base/child classes... well, hope that somebody will understand what I'm asking, if I read this I probably wouldn't mago Edited by - mago on June 10, 2001 9:44:11 AM
thanx for sharing
Advertisement

A solution: don''t make the list the responsible entity for creating/constructing individual objects. Instead use something like:

void blacksmith() {

CSpear *ironspear = new CSpear(...);

PlayerBackPackThatIsAList.add(ironspear);

}

The add method of the list would take a CObject* for argument, since a CSpear* is also a CObject*, this works.

The problem with those generic containers is that when you get the ironspear instance out of the backpack(list), you''ll end up with a CObject* to it, and no further information. How will you figure that you took a CSpear and not some kind of CArmor out of your backpack?

There surely are elegant solutions for this, but I don''t know an ideal solution. This problem is very interesting indeed. "Quake" dealed with this by creating a single "game thing" type called "entity_t". A door in the game would be an entity as much as a player or a flying rocket or an invisible trigger. The difference is that if you wanted to know if an entity instance had some properties, you would have to inspect entity_t''s fields (wich are all the same, so the compiler takes its picky hands out of the matter) like:

someentity.health
someentity.movetype
someentity.flags (one of the bit flags was actually "is a player" or "is a monster")

One downside is that the entity_t has about 200 data fields, and most game objects use only 10% of them. For instance, the ".frags" field is used only by players to keep a track of their individual fragcounts.

I believe that making intrincated "game object" hierarchies is not a task for a C compiler (or any language''s for that matter) to handle. It''s just too dynamic and complicated.

Of course, when you let go compiler automatic typechecking, you have to implement and maintain your own typechecking. Or else players will be able to take their ironspears out of their backpacks and eat them

--
Fabio Reis Cecin
Amok Entertainment
Hey, I''m not "anonymous", it''s me over there!!! )

- Fabio

In other words, Quake implemented Real-Time Type Information (RTTI) with a large structure. I try to design so that I don''t have to use RTTI or similar schemes, but if I had no other way out, I would use the standard C++ RTTI before trying to implement my own. Maybe RTTI wasn''t stable/complete when Quake was written, but it''s there now so it should at least be evaluated as a solution.

As an answer to the original poster, fcecin has the right idea about constructing the object with its specific needs first, then adding it to a generic list. You might have to use RTTI to get information about the object when you grab it from the list, but there can be ways around it.

The best solution (IMHO) is to abstract objects to the point where the base class has functions that generically let you do anything you want with the object. These functions are virtual, so when you make a specific object, it calls the correct virtual function from just a base-class pointer. If you can make all of your objects work this way, you don''t need RTTI.

So you basically have 3 tools:
- keeping data and functions in the object class that are general to all objects (work exactly the same)
- generalizing object functions so that clients can call virtual functions and get the right behavior specified by the appropriate derived class
- using RTTI to get a pointer to the actual derived object, then operating on that.

You can use any combination of these you see fit. Good luck.
Quake was written in C (like all of ID''s games) and therefore no RTTI.

Snale
+--My humble and superior homepage
Thank''s for your help everybody

It''s highly appreciated.
Combining all your posts and adding some of my own ideas I found the way...
thanx for sharing
if your list class is global (eg a singleton) then you could put code to add/remove the object to the list into the constructor/destructor of the CObject class. This would be called automatically when you create the new object.

As for RTTI, the best way to do it is to design your classes and interfaces so you dont need it.

As for having one type fits all, there are certain advantages to this method that go beyond the problems of RTTI. With this method, your objects are almost entirely data driven. This means that if later on, you decide you want to add a GoldenSpear to your game, you can just create one in a file and load it into a standard CObject class, without the need to recompile any code.
Hiya

I''m not positive that this is relevant to what he wants, but when I have a container that keeps a list of abstract objects, I normally create an enumeration with ID''s for all of the different classes that I am using.
enum
{
OBJECT_CLASS,
WEAPON_CLASS,
ARMOR_CLASS,
etc...
}

Then in the base class (CObject) I will have a classID variable. Now when I pull out a CObject from the list, I test the classID variable agains the enumerations and act on it depending on what it is.

void CPlayer::Use( int item )
{
GObject* obj = list.getItem( item );
if (!obj)
return;

int type = obj->getClassID();

switch( type )
{
case ARMOR_CLASS:
wearArmor( (CArmor*)obj );
break;

case WEAPON_CLASS:
sharpenWeapon( (CWeapon*)obj );
break;

default:
break;
};
}

This of course is a fictional class just to show what I mean. lol


Seeya
Krippy
and again, that''s a cheezy C implementation of RTTI. Every time you add, remove, or change a type, you have to go through all of your code. The best way to solve this problem is (as Sandman said) avoid it altogether.

Thanks, didn''t know that about Quake--never looked at the source myself. (Besides, I haven''t liked a FPS since the Aliens Doom 2 mod).
Carmack hates C++ and DirectX, or at least that''s what I gather from what I''ve read.

If CWeapon & CArmor do nothing different than initialize variables differently, then you did a decent job incorporating all the functionality you need into CObject - and consequentially There''s no good reason to make a CArmor or CWeapon class!

CObject* g_BaseWeapon = new CObject;
//Fill in g_BaseWeapon properties here

//later we want to make an actual weapon
COBject* RShortSword = g_BaseWeapon->Clone();
RShortSword->Property("Name")->Value = "Rusty Short Sword";
RShortSword->Property("THAC0")->Value = 20;
//etc... for different types of weapons

//If you had a container of weapons, you could randomly pick one, clone it and call it loot...
CObject* LootWeapon = RShortSword->clone();

Magmai Kai Holmlor
- The disgruntled & disillusioned
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

This topic is closed to new replies.

Advertisement