C++ Workshop - Project 2

Started by
94 comments, last by Warlord_Shaun 15 years, 11 months ago
Is it bad style to include run-time type information into the base class? I'm thinking of adding a data field to my Item class to indicate which derived class does the specific instance belong to, to make handling of inventories conceptually simpler, but it feels wrong somehow.
Advertisement
Quote:Original post by Darkstrike
Is it bad style to include run-time type information into the base class? I'm thinking of adding a data field to my Item class to indicate which derived class does the specific instance belong to, to make handling of inventories conceptually simpler, but it feels wrong somehow.


No, its not bad style. But I fail to see how you're planning to use the RTTI. If you're looking for different behavior based on the specific object, then you can just use polymorphism. Provide an interface for working with all objects in the base class, then provide virtual, overriden functions in each of the derived class. Calling an interface function on a base class pointer to a derived class will perform the function of the matching instanciated object, not the base class.
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
Isn't adding all the interfaces on every descendant of Item wasting memory? I was about to write that it's also producing more code to write, but actually it doesn't unless I have a lot of derived types; however, combining roles would take less code in my version ... which is not in the project, so I'll use superfluous polymorphism. Thanks for opening my eyes to the obvious.
Quote:Original post by Darkstrike
Isn't adding all the interfaces on every descendant of Item wasting memory? I was about to write that it's also producing more code to write, but actually it doesn't unless I have a lot of derived types; however, combining roles would take less code in my version ... which is not in the project, so I'll use superfluous polymorphism. Thanks for opening my eyes to the obvious.


I wasn't encouraging superfluous use of polymorphism, just adequate ammounts. Only create an interface for a function if the function is useful for all derived classes. In this case, you're not creating excess code, you're creating the exact amount you neeed.

You should always put code which is useable by all derived classes in the base class, put interfaces in the base class only when the function makes sense for all derived classes. Otherwise, you can use multiple inheritance to solve the problem. The example in the book is the classic animal, bird, horse scenario. A bird is an animal, and so is horse, so they share common bases such as "eat", "breath", and walk. However, birds can also fly, and horses can also gallop. So what do you do when you want to create a Unicorn? Unicorns are clearly horses and can gallop, but they can also fly which, based on our definition of a bird, qualifies them as birds as well.

One option is to simply copy/paste the fly function from the bird into the unicorn, but this creates excess code which now must be kept up to date. The other option is to move "gallop" and "fly" up into the base class. This would be an example of superfluous polymorphism, because although now you can derive Unicorn from "Animal" and make it both fly and gallop, most animals cannot gallop or fly, so its excess entries in the v-table. The solution, is to create a new type which derives from BOTH bird and horse, and is of type Unicorn. This has the benefit of keeping implementation small, allows you to treat a unicorn as both a horse and a bird, and doesnt add excess entries into the vtable.

One could argue that an even better solution is to use abstract data types for bird and horse, to allow for multiple "interface" inheritance. So you'd just derive Unicorn from Animal, and then also inherit two interfaces "FlyingAnimal" and "GallopingAnimal", which are themselves abstract data types, not derived from animal. Also, you dont have to use pure virtual functions on FlyingAnimal and GallopingAnimal, so they could come with default implementation of Fly() and Gallop().

Anyways, I digress. Dont use superfluous polymorphism when you can aovid it, doing so usually suggest a design flaw.

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
In that case, I don't understand how can I use polymorphism. My impression was that you advised to expose all interfaces in the base class, and those will handle erroneous call attempts. Example: I have an inventory, that contains weapons, armors and potions; this is a std::vector of Item*s, and when the player tells me to wield the chainmail, I need to know whether the Item* is actually a Weapon* before even attempting to do what needs to be done. If all interfaces are in the base class, then I will call the overloaded wield(), which will return an error value that I can recognize. If I have RTTI data in the base class, I can check that before forcibly casting my Item* to Weapon* (or not, if it's inappropriate). But without either of those tools, I really can't think about a way to do this well (splitting the inventory depending on function looks pretty messy to me).
You could have a class Item, for example, as a generic item class. It might have things like how many of the item you have, and, as an example, a Use() function. If you derived a class Weapon from Item, depending on the access restrictions, Item would have everything Item had (let's just say it was all protected). If you defined Use() in Item, it would override the more generic Item::Use(). But, when you're actually using the items, when you call Use() on an item, it looks at what class it is before determining which Use() to use.

But I remember something about 'virtual'... Well, you get the idea. >_>
Quote:Original post by Darkstrike
In that case, I don't understand how can I use polymorphism. My impression was that you advised to expose all interfaces in the base class, and those will handle erroneous call attempts. Example: I have an inventory, that contains weapons, armors and potions; this is a std::vector of Item*s, and when the player tells me to wield the chainmail, I need to know whether the Item* is actually a Weapon* before even attempting to do what needs to be done. If all interfaces are in the base class, then I will call the overloaded wield(), which will return an error value that I can recognize. If I have RTTI data in the base class, I can check that before forcibly casting my Item* to Weapon* (or not, if it's inappropriate). But without either of those tools, I really can't think about a way to do this well (splitting the inventory depending on function looks pretty messy to me).


Ah, now I see what you're asking! =) You're not focusing on functions which exist across all derived classes, you're asking about functions which only exist for 1 of the derived classes. Good question, there are many examples of how to solve this in the textbook in chapter 14 - polymorphism. But here's a simple answer.

Armor and potions can't be wielded, only weapons can, so it makes no sense to give them Wield() functions. As well, most Item's cannot be wielded, so the Wield() function should not be moved up into the base class.

So how do you call Wield() on your Item? Simple, you cant. Polymorphism wont work because Item doesnt contain a Wield() function. You must cast the Item back to a Weapon before you can call Wield(). This of course begs the question, how do I know which Item's are actually Weapon's? Well, one way would be to implement your own Pseudo-RTTI, where the base class has a function called "GetItemType()" or some-such, which returns an enum, identifying the type. If the object is of type WEAPON, then it's safe to cast the Item into a Weapon. Like I said, this is one way...however not the only way, and not necessarily the best way.

C++ actually supports RTTI implicitly, and can usually be enabled in the compiler if it's not enabled by default. With RTTI enabled you can use the nifty "dynamic_cast" operator. And do the following:
vector<item*> Items;Items[0] = new Armor;Items[1] = new Weapon;....for( int i = 0; i < Items.size; i++ ){    Weapon* pTempWeapon = dynamic_cast<Weapon*>( Items );    if( pTempWeapon)    {         ....    }}

In the above example, the dynamic_cast handles the RTTI for you. If the Item in question is of the derived type you're trying to cast to, then it does the cast and returns an object of type Weapon*. However, if the object in question is NOT of that type, then it returns NULL. In which case, pTempWeapon would be an invalid null pointer, and the condition would fail.

So rather than writing an extra function on the base class and having to create an enum for each type of object you have, you can simply try and make the cast, and if it succeeds, you know it's the correct class type. RTTI done for you. =)
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
Oh, and as we'll be working on streams and file I/O next week, it may not be a bad idea to implement your room achitecture in such a way that they can be read from disk.

Perhaps have a Rooms.dat file, or 1 file for each room, ie. bank.room, hallway1, room, grassyfield.room, etc.... Perhaps even a .map, .level, or .maze file which contains all the connections between you rooms. In this way you can create new rooms, and new connections between rooms, without having to recompile your application.

Just a thought. =)
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
You're kidding, right?... please tell me you're kidding...

How long do we have to finish this? [tears]
Nope, not kidding. You've got a month, same as project 1.

It's a more difficult project, but still quite doable in the amount of time. If you're having trouble getting started, or need a shove in the right direction, just post your current thoughts and design, and I'm sure people will offer suggestions.

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints

This topic is closed to new replies.

Advertisement