Jump to content
  • Advertisement
Sign in to follow this  
Astrof

When to use Virtual Functions?

This topic is 3503 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Ok, so a simple Game Design Question (I thought this fit more in "general programming" than game programming): When should I use virtual functions in C++? I moved from Java (where all methods were virtual) to C++ about 6months ago and am trying to design a game. In this game the player has different weapons. Now, each weapon is a different class (extending the base Weapon class) as each weapon has different attributes, etc. So, should I use virtual functions for the GetAttack function or should I use typeid and/or dynamic casting (possibly use some sort of enum to keep track of what type of weapon it is and cast it based on that) and not use virtual functions for these methods? I've been warned to only use virtual functions in the direst of scenarios.

Share this post


Link to post
Share on other sites
Advertisement
First, you should prefer a shallow inheritance tree, or not inheritance at all in this case -- instead, you should think about weapons as a whole in terms of their traits, and then implement these commonalities with a more data-driven design.

The typical deep-hierarchy approach might be to have a base Weapon class, and from it a Sword, Bow, Flail, Spear, Crossbow, and Club classes are derived, and from these classes specific flavors of each might be further derived (ShortSword, LongSword, SwordOfPwning, etc) and this deep derivation can go on add-nauseum (SwordOfRedPwning, etc).

If we make a list of the traits that weapons have, it might look something like this:
- Power
- Range
- Defense
- Accuracy
- Damage Type
- Recover Time
- Success Rate
- Etc.

So we can see now that, at the least, classes of weapons, for instance, Swords, are all just swords with different traits -- The long sword is perhaps just a short sword which is longer and more powerful, but less accurate and with a longer recover time. Perhaps the Sword of Pwning is just a longsword with 3x Power.

We can extend this line of thinking further -- perhaps we like to think that a spear is not so much unlike a sword, after all, in reality its pretty much a short sword at the end of a long stick. Perhaps Flails and Clubs are much like Spears and Swords, except that they do Smashing damage, rather than Cutting/Stabbing damage.

Taking this line of reasoning to its logical conclusion, we might like to notice that even ranged weapons like bows and crossbows can be thought of in terms analogous to swords and spears -- Range becomes the range of the projectile (rather than of the weapon itself), Defense drops to little or nothing (as a bow isn't able to act in a defensive manner), Recover Time becomes reload time, Damage Type becomes Piercing.

If you adopt this final version, no inheritance is needed at all. If you adopt some middle-ground, only one layer of inheritance is necessary -- Thinking in broad categories, the primary weapon classes might be Sword, Melee and Ranged. Which version works for you and your game is up to you, but certainly a shallow inheritance tree is far less unwieldy than one which is very deep. Further, if you rely on inheritance to make your weapons distinct, you will likely come across situations in which multiple inheritance could be applied -- and then you will be faced with duplicating functionality, or opening up the can of worms that is multiple inheritance.


I think that addresses the "design issue" aspect of your question -- as to the question of when to use virtual functions, the answer is easy: You use them when they are the correct tool to implement the design. Virtual functions are used to delegate responsibility to a concrete implementation through a base class pointer. The base class defines an interface which derived classes may (or must, depending on whether or not a default implementation is defined in the base) implement to provide behavior specific to the derived class.


Quote:
Original post by Astrof
So, should I use virtual functions for the GetAttack function or should I use typeid and/or dynamic casting (possibly use some sort of enum to keep track of what type of weapon it is and cast it based on that) and not use virtual functions for these methods? I've been warned to only use virtual functions in the direst of scenarios.


I made the top of this post, and then came back and edited to address this point.

Certainly, you should avoid virtual functions where possible because they incur a performance penalty and also can make debugging, and simply getting your head around your code, more difficult. To this extent, the advice to avoid virtual functions is sound.

However, the avoidance of virtual functions is no excuse to implement your own system for ad-hoc polymorphism. Virtual functions are the standard and built-in method for accomplishing what you would be doing with all your casting.

If the shoe fits, wear it -- There's no reason to role your own when a nice pair of sneakers has already been provided.

Share this post


Link to post
Share on other sites
Note that implementing a faster/leaner virtual function object system in C++ is possible, but (A) it isn't worth it in most every case, and (B) it isn't easy to get right.

Quote:
typeid and/or dynamic casting (possibly use some sort of enum to keep track of what type of weapon it is and cast it based on that)

Don't do that. Dynamic casting and the like is something you should do when you have to do doubly virtual dispatch, and other annoying corner-cases.

Either go with data-driven, or if you prefer, make GetAttack() virtual.

The costs to make a method virtual are not that high, unless you are doing something on a per-pixel and per-frame basis.

Share this post


Link to post
Share on other sites
Wow, thanks, that explains the situation a lot better. Let me clarify my post though, the game I'm making is a 2d scrolling shooter thing (like 1945 or Raiden, etc, first games are usually some 2d cliche game like spaceshooters ;) ). So I'll have a few weapon types (Laser, etc) and will not know at runtime what the player will use.


However, if I understand your suggestion, should I have like, one Weapon class and maybe different functions that set the attributes of the weapon? (Like:
Weapon laser=Weapon::Laser(//attributes go here);

This actually does make a bit more sense. However, what happens if I need to specify some function behavior of each weapon? (IE lasers act differently than missiles or homing shots) so would I make just these functions virtual (if I really needed the function)?

The only reason I ask about virtual functions is in Java (more like in the AP classes) they shoved polymorphism down our throats (everything extended everything, even though some classes weren't even really used on their own; they were just one of many abstract classes used to show class hierarchies).


And hmm, so virtual functions are used with pointers...damn. I try to use pointers only when needed (as I frequently forget to use delete).



EDIT: just saw your post NotAYakk, I think the function could be called on a per frame basis (I mean I (will) check for collision every frame, and if it did collide then I return the attack).

I think I might have to use a virtual function as the attack of the weapon really depends on the type and level of the weapon. I can either use a virtual function of have an epic switch statement and use enums.

Share this post


Link to post
Share on other sites
Quote:
Don't do that. Dynamic casting and the like is something you should do when you have to do doubly virtual dispatch, and other annoying corner-cases.

Doing double dispatch with dynamic_cast is just plain silly.
The visitor pattern exists for a reason.

As for when to use virtual functions, the answer should be obvious when you're using OOP.

Share this post


Link to post
Share on other sites
Quote:
Original post by Astrof
I [...] will not know at runtime what the player will use.

You mean compile time, right?

Quote:
Original post by Astrof
And hmm, so virtual functions are used with pointers...damn.

Just as they are in Java. The difference is that pointers are implicit in Java, and they are called "references" instead.

Weapon x = new Sword(); // x is a pointer to a Weapon, here it points to a Sword



Quote:
Original post by Astrof
I try to use pointers only when needed (as I frequently forget to use delete).

boost::shared_ptr to the rescue!

Share this post


Link to post
Share on other sites
yes sorry I did mean compile time.

hmm I'm a little bit weary of using too many libraries, but I'll look into the boost shared ptr.

Share this post


Link to post
Share on other sites
Quote:
Original post by Astrof
EDIT: just saw your post NotAYakk, I think the function could be called on a per frame basis (I mean I (will) check for collision every frame, and if it did collide then I return the attack).

Something that gets called once per frame is generally not a problem, actually. It's the stuff in your inner loop that gets called hundreds or thousands of times per frame that will most affect your performance (typically, but profiling always has the final say on this). It's extremely unlikely that this particular function is going to have any significant effect on your performance, so worry about doing it correctly, not doing it the way that saves you a couple of cycles of CPU. The overhead of using virtual functions is actually quite small (basically a couple of pointer dereferences per call) so in the vast majority of cases it's better to use it than to reimplement it (probably poorly) yourself. Of course, that's if you need it at all as Ravyne pointed out, but if you do need virtual behavior then virtual functions should be your first stop, not your last.

Quote:
I can either use a virtual function of have an epic switch statement and use enums.

The latter is generally considered poor design and it will bite you at some point in the future when (for example) you add a weapon type and forget to add it to the switch. That way lies madness.:-)

Note that I speak from experience on this point, unfortunately.;-)

Share this post


Link to post
Share on other sites
Quote:
Original post by Astrof
hmm I'm a little bit weary of using too many libraries, but I'll look into the boost shared ptr.

1) You shouldn't be.

2) boost is something like the playground for the next official C++ libraries, shared_ptr will be part of C++0x.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!