Virtual functions and vtbl

This content has 5 years. Please, read this page keeping its age in your mind.

Virtual functions and the magic of calling the correct version at run time, correct? How does it work? This is just a very draft explanation and by no means an extended detailed one. So, the compiler converts the name of the virtual function into an index into a table of pointers, which is called the virtual function table or vtbl. Every class has its own vtbl identifying its virtual functions.

Every class that uses virtual functions (or is derived from a class that uses virtual functions) is given its own virtual table as a secret data member. This table is set up by the compiler at compile time. A virtual table contains one entry as a function pointer for each virtual function that can be called by objects of the class.

Virtual Table is created even for classes that have virtual base classes. In this case, the vtable has a pointer to the shared instance of the base class along with with the pointers to the classes virtual functions if any.

This vtable pointer or _vptr is a hidden pointer added by the Compiler to the base class. And this pointer is pointing to the virtual table of that particular class. This _vptr is inherited to all the derived classes. Each object of a class with virtual functions transparently stores this _vptr. Call to a virtual function by an object is resolved by following this hidden _vptr. Here is a simple example with a vtable representation.

Let’s use an example having three classes Base, Derived1 and Derived2.

#include <iostream>

class Base {
public:
    virtual void function1() { std::cout << "Base::function1()" << std::endl; }
    virtual void function2() { std::cout << "Base::function2()" << std::endl; }
    virtual ~Base() { std::cout << "Base destructor" << std::endl; }
};

class Derived1 : public Base {
  virtual void function1() override { std::cout << "Base::function1()" << std::endl; }
  virtual ~Derived1() { std::cout << "Derived1 destructor" << std::endl; }
};

class Derived2 : public Base {
  virtual void function2() override { std::cout << "Base::function2()" << std::endl; }
  virtual ~Derived2() { std::cout << "Derived2 destructor" << std::endl; } 

};

and the main function:

int main() 
{ 
  Derived1* d1 = new Derived1; 
  Base* b = d1; 

  b->function1();
  b->function2();

  delete(b);

  return 0;
}

output:

Base::function1()
Base::function2()
Derived1 destructor
Base destructor

b pointer gets assigned to Derived1’s _vptr and now starts pointing to Derived1’s vtbl. Then calling to a function1, makes it’s _vptr straightway calls Derived1’s vtable function1 and so, in turn, calls Derived1’s method i.e. function1 as Derived1 has it’s own function1 defined its class.

Where as pointer b calling to a function2, makes it’s _vptr points to D1’s vtbl which in-turn pointing to Base class’s vtbl function2 as shown in the diagram (as D1 class does not have it’s own definition or function2).

So, now calling delete on pointer b follows the _vptr – which is pointing to D1’s vtable calls it’s own class’s destructor i.e. D1 class’s destructor and then calls the destructor of Base class – this as part of when dervied object gets deleted it turn deletes it’s emebeded base object.

Source: https://www.go4expert.com/articles/virtual-table-vptr-t16544/