Virtual Functions in Cpp

Posted in Programming cpp The Weeklies -

This week, I decided to start off a series of mini posts, which is called “The Weeklies”. It’s based on the ideas of The Dailies, an article I stumbled into while trying to find stuff that can help me improve my C++ level. I don’t really have time to do this thing daily, and with the remote working situation, it’s not realistic to do that in the office either, so I decided to give it a try with weekly (or as frequent as I can) posts on my blog.

A little bit of background: C++ has never really been my strong point: I know something about it, but cannot say I feel really fluent. (An example of how “good” I am with this language: My current job has a very large code base written mostly in C++, which I have always wanted to understand, but still am clueless after several tries.) This series is my attempt to improve both my knowledge in C++ and my blog, as I haven’t written anything here for a long time.

The first topic we’ll cover is, as the title: Virtual Functions in C++. The article was written by me, but only after I have consumed a lot of resources on https://www.learncpp.com/

Polymorphism is weird

Let’s start with recapping briefly how inheritance works in C++.

Suppose you have a simple class called “Animal” as followed:

class Animal
{
private:
    std::string m_name;
public:
    Animal(std::string name){
        m_name = name;
    }
    std::string getName() { return m_name; }
    std::string speak() { return "???"; }
};

int main()
{
    Animal animal { "Mickey" };
    std::cout << "Animal is named " << animal.getName() << ", and it says " << animal.speak() << "\n";
    return 0;
}

Compiling and running that code gives us:

Animal is named Mickey, and it says ???

Now let’s add another class called Dog, which inherits Animal.

class Animal {...}

class Dog: public Animal
{
public:
    Dog(std::string name)
        : Animal{ name }{}
    std::string speak() { return "Woof"; }
};

int main()
{
    Dog animal { "Mickey" };
    std::cout << "Animal is named " << animal.getName() << ", and it says " << animal.speak() << "\n";
    return 0;
}

This returns:

Animal is named Mickey, and it says Woof

This is how inheritance is supposed to work. When dog.getName() was called, it didn’t find the function in class Dog, but since Dog inherits Animal, and Animal has a getName() function, Animal.getName() was called; on the other hand, since Dog does have a speak() function, Dog’s version of speak() (which says “Woof”) was called, not the speak() function in Animal class.

Also, please notice that except for the change in object type (change from Animal to Dog), the main() function in both cases are almost identical. It’s called polymorphism: the program doesn’t care if the object is of Animal type or any type that inherits from Animal, as long as that object has these two functions (getName() and speak())

Polymorphism is a kinda sophisticated word to imply that it’s not known beforehands whether an object is an instance of a (“base”) class, or an instance of a class that inherits that “base”.

So far so good, eh?

Ok, you may have noticed that the example above may not be considered true polymorphism, as everything we’ve done happens inside the main() function, where we know beforehands whether animal is an instance of Dog or Animal. Let’s try putting that std::cout line to its own function, which should take an animal object as its only parameter.

class Animal {...}

class Dog: public Animal {...}

void echo(Animal animal){
    std::cout << "Animal is named " << animal.getName() << ", and it says " << animal.speak() << "\n";
}

int main()
{
    Dog dog { "Hugo" };
    echo(dog);
    Animal animal { "Mickey" };
    echo(animal);
    return 0;
}

And that gives us:

Animal is named Hugo, and it says ???
Animal is named Mickey, and it says ???

What just happened? Did Hugo forget he was a dog or what?

Turns out, as Dog class inherits Animal, an instance of Dog is also consider an instance of Animal. The function echo() receives an Animal class instance, so it simply calls Animal.speak(), even on Hugo, who is a dog (and is an instance of class Dog).

If you suspect this happens because we pass the object as a value (instead of using a pointer) to echo(), try a pointer instead to see how it changes the result?

So how do we do Polymorphism?

At this point, you may have wondered: how would we make Hugo a dog again then? The answer is quite simple: use the “magic” word virtual on the functions of the that might be re-implemented by children.

class Animal
{
private:
    std::string m_name;
public:
    Animal(std::string name){
        m_name = name;
    }
    std::string getName() { return m_name; }
    virtual std::string speak() { return "???"; }
};

class Dog: public Animal {...}

void echo(Animal *animal){
    std::cout << "Animal is named " << animal->getName() << ", and it says " << animal->speak() << "\n";
}

int main()
{
    Dog dog { "Hugo" };
    echo(&dog);
    Animal animal { "Mickey" };
    echo(&animal);
    return 0;
}

Result:

Animal is named Hugo, and it says Woof
Animal is named Mickey, and it says ???

Hugo finally knows he is a dog.

So that’s basically what the virtual keyword does. It signals the compiler that this might not be the final implementation of the function, so keep looking if there’s a more derived version of it (of course, if not then the current version will still be used, which means you can add virtual to getName() as well, but nothing should change in the result.

That’s it for today. Thanks for reading!

Written by Huy Mai