DEV Community

Cover image for Virtual functions in C++
Mattia Bonicelli
Mattia Bonicelli

Posted on

Virtual functions in C++

A virtual function in C++ is a function declared in a base class that allows overriding by its derived classes.

The main benefit of using virtual functions is the ability to achieve runtime polymorphism.

Without declaring a virtual function, if we were to try and override a regular function

class Animal {
public:
    void describe_self() const {
        std::cout << "I'm an animal" << std::endl;
    }
};

class Dog : public Animal {
public:
    void describe_self() const {
        std::cout << "I'm a dog" << std::endl;
    }
};

class Cat : public Animal {
public:
    void describe_self() const {
        std::cout << "I'm a cat" << std::endl;
    }
};
Enter fullscreen mode Exit fullscreen mode

and then we were to assign an object of class Dog to an Animal's pointer

int main()
{
    Animal* doug = new Dog();
    doug->describe_self();
}

Enter fullscreen mode Exit fullscreen mode

since the C++ compiler can only determine the type of the pointer at compile time it will bind the Animal pointer's describe_self function call to the method inside our base Animal class.

As such the following will print to console.

I'm an animal
Enter fullscreen mode Exit fullscreen mode

This is because memory addresses of the Animal pointer and the Dog object are only resolved at runtime. This behaviour is known as early binding.

To prevent this from happening, and instead execute the most derived version of the called class, we can use a virtual function by adding the keyword virtual inside our base class method.

class Animal {
public:
    virtual void describe_self() const {
        std::cout << "I'm an animal" << std::endl;
    }
};
Enter fullscreen mode Exit fullscreen mode

This will correctly print

I'm a dog
Enter fullscreen mode Exit fullscreen mode

It is also a good idea to add the C++11 keyword override in the derived classes methods for clarity and to prevent typos.

class Dog : public Animal {
public:
    void describe_self() const override {
        std::cout << "I'm a dog" << std::endl;
    }
};

class Cat : public Animal {
public:
    void describe_self() const override {
        std::cout << "I'm a cat" << std::endl;
    }
};
Enter fullscreen mode Exit fullscreen mode

Runtime polymorphism can be used for example in scenarios where you would want all of your objects from derived classes to execute the same method.

int main()
{
    Animal* doug = new Dog();
    Animal* kat = new Cat();

    Animal* pets[2] = { doug, kat };

    for (int i = 0; i < 2; i++) {
        pets[i]->describe_self();
    }
}
Enter fullscreen mode Exit fullscreen mode

This will print

I'm a dog
I'm a cat
Enter fullscreen mode Exit fullscreen mode

Here we create an array of Animal pointers mapped to different Animal derived classes' objects, and we call their own describe_self method in a succint and scalable way.

There are also pure virtual functions that declare a placeholder method.

class PureVirtual {
public:
    virtual void function() const = 0;
};
Enter fullscreen mode Exit fullscreen mode

This abstract class forces any derived class to implement its own version of that function.
 
 

When a virtual function is declared and an object is instantiated from a class implementing it, the compiler will add a virtual pointer to the class object's data members and map it to a virtual table.

This virtual table is usually added at the class level, and it is an array containing pointers mapped to each virtual function.

To determine which function to call the runtime will follow these virtual pointers to the virtual table and finally to the function meant to be invoked.

In the above code our Dog and Cat class will each have a virtual table each containing an entry pointing to the describe_self method and our doug and kat objects will have a virtual pointer mapped to respectively our Dog and Cat's virtual tables.

Due to these extra steps virtual functions come at an extra cost, both in size and in speed.

The virtual tables in the classes, and the virtual pointers in the objects will take up extra memory, and for each function call at runtime we will have to follow the virtual pointers to the virtual table before reaching the correct address.

In short, virtual functions are a way to keep the code base more readable and more compact, but they incur a small cost that may be a factor when run on low-power embedded chips that can't sacrifice CPU cycles or other similar scenarios.

Top comments (1)

Collapse
 
pauljlucas profile image
Paul J. Lucas • Edited

Due to these extra steps virtual functions come at an extra cost, both in size and in speed.

"Extra," but relative to what alternative? The typical alternative of storing a "type" data member and switching on that value is generally worse (since the switch has to be done in many places). It's a bit of a disservice to state that virtual functions come at an extra cost without also mentioning that the alternatives also have a cost. There's no free lunch.