Jump to content

  • Log In with Google      Sign In   
  • Create Account

- - - - -

C++ Workshop - Project 2

  • You cannot reply to this topic
95 replies to this topic

#21 Darkstrike   Members   -  Reputation: 206

Like
0Likes
Like

Posted 29 September 2006 - 03:23 AM

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.

Sponsor:

#22 JWalsh   Moderators   -  Reputation: 463

Like
0Likes
Like

Posted 29 September 2006 - 03:35 AM

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

#23 Darkstrike   Members   -  Reputation: 206

Like
0Likes
Like

Posted 29 September 2006 - 04:26 AM

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.

#24 JWalsh   Moderators   -  Reputation: 463

Like
0Likes
Like

Posted 29 September 2006 - 05:09 AM

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

#25 Darkstrike   Members   -  Reputation: 206

Like
0Likes
Like

Posted 29 September 2006 - 05:53 AM

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

#26 Twisol   Members   -  Reputation: 468

Like
0Likes
Like

Posted 29 September 2006 - 06:14 AM

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

#27 JWalsh   Moderators   -  Reputation: 463

Like
0Likes
Like

Posted 29 September 2006 - 06:15 AM

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[i] );
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

#28 JWalsh   Moderators   -  Reputation: 463

Like
0Likes
Like

Posted 29 September 2006 - 09:36 AM

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

#29 Twisol   Members   -  Reputation: 468

Like
0Likes
Like

Posted 29 September 2006 - 09:43 AM

You're kidding, right?... please tell me you're kidding...

How long do we have to finish this? [tears]

#30 JWalsh   Moderators   -  Reputation: 463

Like
0Likes
Like

Posted 29 September 2006 - 10:01 AM

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

#31 Twisol   Members   -  Reputation: 468

Like
0Likes
Like

Posted 29 September 2006 - 02:12 PM

Heh, I'm still working on Project 1! (My journal has a to-do list and a few comments)

#32 westond   Members   -  Reputation: 122

Like
0Likes
Like

Posted 29 September 2006 - 07:56 PM

Quote:
Original post by jwalsh
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. =)



Hrm, looks like I should read before throwing any code out there. Ive no clue about how to do that. This makes my 6th or 7th week in toto of programing since comodore basic in the early 80's /shame


#33 Zahlman   Moderators   -  Reputation: 1682

Like
0Likes
Like

Posted 29 September 2006 - 10:18 PM

Quote:
Original post by jwalsh
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.


For completeness, other common ways of approaching this kind of situation are:

1) Just don't put a Weapon* into a container of Item*s if you are going to care about its Weapon-ness later. Have a separate container just for Weapons instead. Of course, this can get messy quickly, especially considering that almost all types are just as likely to need that kind of "special treatment". Yes, you get the ability to just pull a Sword out of the Room's Weapon-vector, and put it in the Player's Weapon-vector and not worry about the types... but the classes quickly get very complicated. On the gripping hand, note that if Weapon is a most-derived class, you could store Weapons directly rather than Weapon*'s - but that's unlikely to help you (will probably increase your memory management headaches rather than decreasing them) if you're already using pointers in most of the rest of the system.

2) Instead of a direct type-query like GetItemType(), create capability queries, like isWieldable() which might return true for Weapons and false for all other Items. This gives you extra flexibility, because you could e.g. make Potions, Wands and Scrolls - but not Armor or Weapons - be isHoldable(). Sometimes, this points to a need to add a level to the inheritance hierarchy (i.e. HoldableItem), but sometimes it's not that clear-cut. But if you can avoid this approach (in favour of a better-designed inheritance scheme and/or the build-in RTTI with dynamic_cast), then you probably should avoid it. It often quickly degenerates into implementing your own RTTI instead of letting the language do it for you, and anyway you usually want member functions to do stuff™ when possible.

There are always trade-offs to be made. Getting a good design will require you to Think™. Happy hacking (cheers Olu).

#34 JWalsh   Moderators   -  Reputation: 463

Like
0Likes
Like

Posted 30 September 2006 - 03:33 AM

Thanks Zahl. I knew I forgot some options. =) Both of your suggestions are good possible alternatives.

[OPINION]
When trying to figure out the best design guys, just remember good engineering philosophies. You want it encapsulated, modular, and easily maintainable. This often amounts to doing it whichever way requires the least amount of user code, as this frequently means less work on your behalf, less to encapsulate, less to modularize, and less to maintain.

If the language or compiler does something for you...let it. =)
[/OPINION]
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

#35 RinusMaximus   Members   -  Reputation: 122

Like
0Likes
Like

Posted 30 September 2006 - 04:56 AM

Wow, this project is even better than I had expected. I have been on a training in Belgium the last week so I didn't have time to read this earlier.

I'm gonna start on it immidiately. Good job Jeromy!

#36 kingIZZZY   Members   -  Reputation: 122

Like
0Likes
Like

Posted 30 September 2006 - 06:09 PM

I thought the other characters in the game (not you) would be created randomly, and destroyed when exiting a room. Then other random characters will or wont be created should you re-enter the same room. but then theres this:

Quote:
Original post by jwalsh
...All coins are taken by the character that defeated you (and can later be regained by defeating them)...8b. Attacking a mob and then running flags that mob as hostile. The next time you enter the room, the enemy will continue their attack, however you automatically gain the initiative and get the first swing.


oh, so characters are static, or, at least the one who engaged me. should all other characters also be static? and only re-created randomly after player defeats a character? seems abit weird though, what if a hostile character decides to go home? but no, he cant, he must stay around until player engages him again because he is flagged as hostile, or because he's holding my coins...somewhat weird

#37 Zahlman   Moderators   -  Reputation: 1682

Like
0Likes
Like

Posted 30 September 2006 - 07:18 PM

Quote:
Original post by kingIZZZY
I thought the other characters in the game (not you) would be created randomly, and destroyed when exiting a room. Then other random characters will or wont be created should you re-enter the same room.


I don't see where you get that from. I would say that the set of characters in the game is generated randomly at the start (possibly with more added at time and/or turn intervals), and when you leave a room, everything that was there just stays there until you come back.




Anyway, I got the go-ahead to show you guys this thing I was working on recently. (To understand the name, you will need some background.) This may be a useful way of handling those Items: it's a way (actually a combination of general-purpose C++ techniques) of handling the memory management and getting objects (rather than pointers) to show polymorphic behaviour (it simply works by making an object that's a wrapper for the pointer; it does the memory management on the pointer, and uses the pointed-at object in order to behave like an object of that derived type). Please try to understand everything if you want to use this :)

In that setup, you should be able to handle dynamic_casts with code along the lines of (integration is left as an exercise):


// Return a reference so that we can keep on using the object syntax.
// (Polymorphism works upon references the same as on pointers.)
// Notice that dynamic_cast can cast references too, but because there is
// really no such thing as a "null reference", it doesn't have an effective
// return value to indicate an error. Therefore, a failed cast throws an
// exception - std::bad_cast, IIRC. If this is unacceptable, to you, you might
// want to bite the bullet and return a pointer (implementation left as
// exercise).
template<typename Impl>
Impl& Animal::as() {
return dynamic_cast<Impl&>(*impl);
}

// Used like...
#include "Animal.h"
#include "Chicken.h"

Animal x = someCodeCreatingOrFindingAnAnimal();
// I "know" x is a Chicken...
x.as<Chicken>().scratchInDirt();



#38 RinusMaximus   Members   -  Reputation: 122

Like
0Likes
Like

Posted 30 September 2006 - 08:36 PM

Does the std library have a linked list? I have been looking for a good implementation but I can't find one.

I thought a linked list would be the best way of storing my Room's, seeing that I have to iterate over them a lot.

#39 Deyja   Members   -  Reputation: 920

Like
0Likes
Like

Posted 01 October 2006 - 01:15 AM

Of course it does. It's in header '<list>', and it's called 'std::list'.

#40 RinusMaximus   Members   -  Reputation: 122

Like
0Likes
Like

Posted 01 October 2006 - 02:48 AM

Quote:
Original post by Deyja
Of course it does. It's in header '<list>', and it's called 'std::list'.


Great, thanks.







PARTNERS