Virtual function is a special member function, when called on the base class object pointer or reference, resolves to the member function of the most derived class.

For example:

class Base
{
    virtual void callme();
    {}
};
 
class Derived: public Base
{
    virtual void callme();
    {}
};
 
Derived d;
 
Base& ref{d};
 
ref.callme();

ref.callme() calls the derived class callme member function because it is made virtual.

With virtual function, we can solve the issue that we faced in the Shape example in this note.

#include <iostream>
#include <vector>
 
class Shape
{
    int m_sides{};
 
public:
    Shape() = default;
 
    Shape(int sides)
        : m_sides{sides}
    {
    }
    Shape(const Shape &) = delete;
    Shape &operator=(const Shape &) = delete;
 
    virtual void about() const
    {
        std::cout << "Shape: ???" << '\n';
    }
};
 
class Square : public Shape
{
public:
    Square()
        : Shape(4)
    {
    }
 
    virtual void about() const
    {
        std::cout << "Shape: Square" << '\n';
    }
};
 
class Triangle : public Shape
{
public:
    Triangle()
        : Shape(3)
    {
    }
 
    virtual void about() const
    {
        std::cout << "Shape: Triangle" << '\n';
    }
};
 
void printShapeMeta(std::vector<Shape *> shapes)
{
    for (const Shape *shape : shapes)
    {
        shape->about();
    }
}
 
int main(int argc, char const *argv[])
{
    Square s;
    Square s2;
    Triangle t;
    Triangle t2;
 
    std::vector<Shape *> shapes{&s, &t, &s2, &t2};
 
    printShapeMeta(shapes);
    return 0;
}

Executing this program gives expected results:

Shape: Square
Shape: Triangle
Shape: Square
Shape: Triangle

How does it work?

When we make member function virtual and call the function on reference or pointer of base type referencing or pointing object of derived type, C++ looks for a matching member function on the most derived class. In this case, C++ finds about() in derived classes Square and Triangle.

This lookup happens at run time and thus it is called runtime polymorphism. We have already seen function overloading that happens at the compilation time. Function overloading is called compile time polymorphism.

Note

Few things to note about virtual functions:

  1. The function signature and return type has to be same for member functions inside base and derived classes to make it work.
  2. Just making base class member function virtual also works, but it does not true vice versa.

Avoid calling virtual functions from constructors and destructors

When we call virtual function inside the constructor of the base class, C++ would call the base class version of the function. As we know that derived class constructor is only called after the base class constructor is finished, when virtual function is called inside the base class constructor, there is no complete derived object created to invoke function on. So, C++ calls the base version function.

Similarly, if we call virtual function inside base class destructor, the derived class destructor has already been called and object derived portion is already destroyed. So, C++ calls the base version.

Issues with virtual functions

  1. Resolving virtual functions are inefficient and take longer time than the regular functions.
  2. To work with virtual functions, C++ requires an extra pointer for the virtual function which increases the class size.

References

  1. https://www.learncpp.com/cpp-tutorial/virtual-functions/