Polymorphism in C++

If you are a new C++ programmer, I’m pretty sure that you at least heard about the term “polymorphism” but maybe you don’t quite fully understand what it is or how it works.

Don’t worry, I got you. There’s an explanation below on what is polymorphism, what types of polymorphism there are and what happens in the “back end”(e.g. how the compiler implements dynamic polymorphism).

What is polymorphism?

Polymorphism in C++ refers to the fact that an entity(function or object) can perform differently in different scenarios.

The term “polymorphism” is strongly associated in C++ with dynamic polymorphism but there can also be static polymorphism.

Static polymorphism

Static polymorphism is also known as compile time polymorphism and it refers to the fact that a function will have it’s behavior decided at compilation time. When it gets to runtime, there is no processing to be done for finding the right function implementation to call.

It is achieved by:

  • function overloading
  • operator overloading
  • templates
// function overloading
double do_oper(int a) // #1
{
    return a * 2;
}

double do_oper(double a) // #2
{
    return a * 2;
}

auto oper1 = do_oper(5); // #1 is called
auto oper2 = do_oper(5.0); // #2 is called
// operator overloading(already provided by compiler)
auto sum = 1 + 2; // operator+ adds the integers
auto concat = std::string{"1"} + std::string{"2"}; // operator+ concatenates the strings
// template
template <class T>
T do_sum(T a, T b)
{
    return a + b;
}

auto sum_int = do_sum(1, 2); // int do_sum(int a, int b) is called
auto sum_double = do_sum(1.0, 2.0); // double do_sum(double a, double b) is called

Implementation with all the above examples can be found on godbolt. I’ve used no optimizations for the compile(-O0) in order for you to see the calls done.

Dynamic polymorphism

Dynamic polymorphism is also known as runtime polymorphism and it refers to the fact that a function will have it’s behavior decided at run time. The compiler does not know from the compile time what function implementation to call.

It can be achieved by:

  • function overriding (virtual functions)

Contrary to the function overloading which can occur with or without inheritance, function overriding can only occur in context of inheritance.

The virtual keyword basically tells the compiler that the function might be redefined in derived classes and it should look for the proper implementation at run time.

class Base
{
public:
    int a;
    virtual void doSomething()
    {
        std::cout << "doSomething from Base\n";
    }

    virtual void doOtherThing()
    {
        std::cout << "doOtherThing from Base\n";
    }
};

class Derived : public Base
{
public:
    int b, c;
    void doSomething() override // override implies virtual
    {
        std::cout << "doSomething from Derived\n";
    }
};

std::shared_ptr<Base> b{std::make_shared<Derived>()};
b->doSomething(); // prints "doSomething from Derived"

As you can see, since the pointer b is a pointer of type Base but it actually points to an object of type Derived, the implementation of doSomething() from Derived, should and, is called.

Full example on godbolt.

Compiler’s take on virtual functions

So, in order to achieve dynamic polymorphism, we need virtual functions.

The question is, how does the compiler look for the proper implementation of the virtual function that should be called at runtime.

This is where the vtable and vptr come into play.

vtable and vptr

The vtable is a table(static array) which contains the memory addresses of all virtual functions of a class in the order in which they are declared in a class.

This table is used to resolve function calls in dynamic manner. Every class that has virtual function will get its own vtable.

The vptr is a pointer that stores the address of the vtable.

When you create an object of a class which contains virtual function then a hidden pointer gets created automatically in that object by the compiler. That pointer points to a virtual table of that particular class.

As far as I remember, the vptr and vtable are not mentioned anywhere in the C++ standard and are just a way for C++ compilers to implement dynamic polymorphism that has been adopted widely(probably all C++ compilers use this way of implementing dynamic polymorphism).

Let’s take the above example with Base and Derived. Object for each type will have this structure:

If we use -O0 as compile flag for our previous example, we can see the vtables on godbolt.

For the future

First of all, I hope I’ve shed a little bit of light on the entire polymorphism topic and you learned something new here. I don’t think I covered every single thing about polymorphism but this should be more than enough for you to have an understanding on polymorphism and how it works.

In the next articles I will focus a little bit on why it’s better to avoid dynamic polymorphism if performance is a critical factor. You will see that dynamic polymorphism affects the performance of your application and why.

So, keep tuned :).

1 thought on “Polymorphism in C++”

  1. Pingback: Dynamic polymorphism performance - cppdev

Leave a Reply

%d bloggers like this: