07 June 2015

So why do we need virtual functions anyway?

For most of us, something makes sense only when we know the reason behind it.
So when our textbooks and tutorials show us something like...

class Animal {
    public:
    virtual void sound() = 0;
};

class Dog : public Animal {
    public:
    void sound() { std::cout<<"bark\n"; }
};

It just doesn't make sense. Why would I want to use such an animal class when I can simply create a separate Dog class and a Cat class? Even if the base class was a Shape and derived classes were Rectangle and Triangle, I can override the base class function without using virtual. Then why virtual?

Before proceeding, I'd like to introduce you to Initialization lists.
In C++, you are allowed to initialize values to member variables using the initialization list syntax. See this:

class Abc {
    int a;
    SomeClass* s;
    Abc() : a(10), s(new SomeClass())
    {
         //constructor body
    }
};

The value of a becomes 10 and the pointer to the newly created SomeClass is initialized into s. Note that they are not assigned to a and s. They are initialized into the member variables.


Who needs virtual?

If you're a beginner programmer, you don't really need to worry much about virtual functions. It's unlikely you'd be using much of it anyway. But if you're an intermediate or advanced programmer who has come to realize that creating software takes just 10% of your time and 90% of time will be spent in altering the code and maintaining the software through its lifecycle, then you definitely need to understand why virtual functions can help you write de-coupled, flexible, encapsulated, maintainable code.


An example:

Let's say you've written a very complex Battlefield Software which is being used on a defence truck. The software is capable of monitoring devices on the truck, communicating with the army command control center situated far away and is also capable of doing a lot of other things.

One other function of Battlefield Software is to issue a command to a gun mounted on the truck to fire ammunition. Normally, you'd create a Gun1.fire() function in Battlefield Software which directly invokes the gun's fire() function.



But what will you do when this gun is removed and a missile launcher or a gun from some other company is fitted? Those weapons have different functions for shooting, and those companies refused to change their function names to fire().


You'd have to go into Battlefield Software and change Gun1.fire() to Gun2.shoot() or MissileLauncher.launch(). Then compile everything again and test it all over again.


So what's the problem?

You tell me: "Hey no problem. I don't mind changing the function. It's just one function after all".

But you don't see the full picture here. Gun1.fire() might not be the only command that Battlefield Software uses. There might be many other places in the software where the Gun1 object instance is being invoked. There might be Gun1.calibrateGun() command, Gun1.positionGun() command, Gun1.reload() command and so many more.

Are you going to change all of them? In real-world softwares, the code becomes so complex, that it wastes a huge amount of time making corrections like these. Once such corrections are made, they have to be tested too, because you might have un-knowingly made some change and introduced a bug.


The first part of the solution...

... is to create a separate software module named WeaponController which will purely be in charge of interfacing with the weapon mounted on the truck.


Now, BattlefieldSoftware can be programmed to contain an instance of WeaponController, and to shoot a target, BattlefieldSoftware just has to call WeaponController.shootTarget().

The WeaponController class will decide whether to call fire() or launch() or shoot(). So even if a gun from a new company is brought in, the changes only have to be made to the small WeaponController program. The compilation will be faster and the testing can also be done quickly, because you are only modifying WeaponController, so you are sure there won't be any bugs introduced into the big BattlefieldSoftware program.


Implementation

Now comes the interesting part. How do you propose to implement WeaponController?
One way you'd suggest is this (it's a simple implementation just to keep it short):


class BattlefieldSoftware {
    private: 
    WeaponController* wc;
    public:
    BattlefieldSoftware() : wc(new WeaponController()) {} //initialization list for constructor         
    ~BattlefieldSoftware() { delete wc; }

    void shootTarget() { wc->shootTarget(); }
};

class WeaponController {
    private:
    Gun1* g1;
    //Gun2* g2;
    //MissileLauncher* ml;

    WeaponController() : g1(new Gun1()) /*, g2(new Gun2), ml(new MissileLauncher) */
    {}
    ~WeaponController() { delete g1; /*delete g2; delete ml*/ }

    public:
    void shootTarget() {
         if (g1 != NULL) { g1->fire(); } 
         //if (g2 != NULL) { g2->shoot(); }
         //if (ml != NULL) { ml->launch(); }
    }
};


So this is what you propose to do? Have a bunch of if conditions or switch statements which will decide which weapon to fire? Or to comment out the irrelevant weapons and re-compile?

Well, there's nothing wrong in doing it this way. It'll work without virtual functions and will work a little faster than virtual functions too, because the program won't have to refer a virtual table during runtime to decide which function it should invoke.

But notice that again, you're creating more scope for bugs and will have to repeatedly test your program because the actual program might be much much bigger than this and you can't guarantee that you have commented out all relevant lines or not unknowingly introduced some new bug.


A better way...

The better way to do it, is to create wrapper classes (WeaponGun1, WeaponLauncher, WeaponGun2) for every weapon and interface them with WeaponController.
The wrapper classes help behave like translators. When WeaponController says shootTarget(), the WeaponGun2 wrapper class will help call the shoot() function of Gun2.
Why does that help?
Because when we attach a particular gun or missile launcher to the weapon controller (we can attach only one at a time in this example), we want WeaponController to simply be able to invoke shootTarget(). If we didn't have wrapper functions, WeaponController would have to be re-programmed to invoke shoot(), launch() or fire(), everytime we attach a new weapon. We want to avoid the hassle of re-programming and re-compiling whenever we make a change.




Virtual functions to the rescue

Have a look at the new code first.

#include "iostream"

//This class is created by Gun1's company
class Gun1 {public: void fire() {std::cout<<"gun1 firing now\n";}};
//This class is created by Gun2's company
class Gun2 {public: void shoot() {std::cout<<"gun2 shooting now\n";}};


//We create an abstract class to interface with WeaponController
class WeaponsInterface {
 public:
 virtual void shootTarget() = 0;
};

//A wrapper class to encapsulate Gun1's shooting function
class WeaponGun1 : public WeaponsInterface {
 private:
 Gun1* g;

 public:
 WeaponGun1(): g(new Gun1()) {}
 ~WeaponGun1() { delete g;}
 virtual void shootTarget() { g->fire(); }
};

//A wrapper class to encapsulate Gun2's shooting function
class WeaponGun2 : public WeaponsInterface {
 private:
 Gun2* g;

 public:
 WeaponGun2(): g(new Gun2()) {}
 ~WeaponGun2() { delete g;}
 virtual void shootTarget() { g->shoot(); }
};

class WeaponController {
 private:
 WeaponsInterface* w;
 WeaponGun1* g1;
 WeaponGun2* g2;
 public:
 WeaponController() {g1 = new WeaponGun1(); g2 = new WeaponGun2(); w = g1;}
 ~WeaponController() {delete g1; delete g2;}
 void shootTarget() { w->shootTarget();}
 void changeGunTo(int gunNumber) {
//Virtual functions makes it easy to change guns dynamically
   switch(gunNumber) {
     case 1: w = g1; break;
     case 2: w = g2; break;
   }
 }
};


class BattlefieldSoftware {
 private:
 WeaponController* wc;
 public:
 BattlefieldSoftware() : wc(new WeaponController()) {}
 ~BattlefieldSoftware() { delete wc; }

 void shootTarget() { wc->shootTarget(); }
 void changeGunTo(int gunNumber) {wc->changeGunTo(gunNumber); }
};


int main() {
 BattlefieldSoftware* bf = new BattlefieldSoftware();
 bf->shootTarget();
 for(int i = 2; i > 0; i--) {
     bf->changeGunTo(i);
     bf->shootTarget();
 }
 delete bf;
}




The advantage

Phew! Lengthy way of doing it, right? But see...now that I've created class WeaponGun1, I've completely separated the handling of Gun1 into the class. Whatever changes you do to Gun1, you'll only have to make changes in WeaponGun1, and have the confidence that no other class is affected.

Because of WeaponsInterface class, I can now assign any derived class to the base class pointer WeaponsInterface and because it's functions are virtual, when I call WeaponsInterface's shootTarget, the derived class shootTarget gets invoked.

Best part is, I can change guns during runtime (the code in green). This is the main advantage of virtual functions and this is why we need virtual functions.

So no more necessity to comment out code in various places when changing guns. It's now a simple and clean procedure, and adding more gun classes is also easier because we just have to create a new WeaponGun3 or WeaponGun4 class and we can be confident that it won't mess up BattlefieldSoftware's code or WeaponGun1/WeaponGun2's code.

WeaponsInterface class is the equivalent of Animal class.
WeaponGun1 class is the equivalent of Dog class.

Makes more sense as weapons class, doesn't it? :-)


One more big help:
One more little thing. In this tutorial, did you notice how you learnt something new and useful about how to structure your code so that it will be more maintainable? Well, software architects have identified many such situations and created absolutely awesome ideas for structuring code. These ideas are completely worth learning. You'll actually be impressed about how effective they are. Once you've matured a bit in programming, make sure you get a good book on Design Patterns and learn them. In real-life even if you try to use design patterns, code can get messed up due to tight deadlines, but it still helps to know those concepts.





Say thank you or donate

10 comments:

Unknown said...
This comment has been removed by the author.
Unknown said...

Never mind the previous comment. I misunderstood your example. This is a good explanation. Thank you very much.

Nav said...

Thank you Yangting Yu; and you are most welcome.

Nav said...

Glad it helped :-)

Unknown said...

Great! Thank You

Kishore said...

that's great help, thank you very much!

Eric said...

Thanks Navin, for the wonderful explanation.

Unknown said...

Hey, This is excellent example found, else all explaining bookish and repeated knowledge.
I am very good clear.

Anonymous said...

SPLENDED!
~from Michigan

Unknown said...

Very cool sir. Thank you so much.