Sunday, June 7, 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 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 Navin. 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 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.


But a still better way to do it, is to create wrapper classes (WeaponGun1, WeaponLauncher, WeaponGun2) for every weapon and interface them with WeaponController.



Implementation

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


class BattlefieldSoftware
{
    private: 
    WeaponController* wc;
    public:
    BattlefiledSoftware() : 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 class 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.


Virtual functions to the rescue

Have a look at the new code first. The BattlefieldSoftware class is unchanged.



#include "iostream"

//This class is created by Gun1's company

class Gun1 {public: void fire() {std::cout<<"gun1 firing 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(); }
};

class WeaponController
{
    private:
    WeaponsInterface* w;
    public:
    WeaponController(): w(new WeaponGun1()) {}
    ~WeaponController() {delete w;}
    void shootTarget()
    {
        w->shootTarget();
    }
};


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

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


int main()
{
    BattlefieldSoftware* bf = new BattlefieldSoftware();
    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.

So now when I want to change guns, the only place I have to change it is in the code highlighted in green. Best part is, I can change guns during runtime by having if-else or switch cases which will assign guns at runtime. That's the best part about 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 WeaponGun2 or WeaponGun3 class and we can be confident that it won't mess up BattlefieldSoftware's code or any of the other WeaponGun'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 :-)



Some people have emailed me asking if they could thank me for having given them knowledge. The best way to thank me is by contributing to Open Source. Being a sweetheart if you'd like to give a more personal thank you, then I don't really like the idea of monetary donations, but  maybe a wishlist wouldn't be that bad.

5 comments:

楊庭宇 said...
This comment has been removed by the author.
楊庭宇 said...

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

Navin Ipe said...

Thank you Yangting Yu; and you are most welcome.

Алексей Гончаренко said...

Currently I am learning and trying to understand virtual functions. And I think that your explanation about virtual functions and their purpose is great! It helped me to understand. Navin, thanks a lot!

Navin Ipe said...

Glad it helped :-)