C++ standard only specifies what is the expected behavior of virtual functions, which is calling the most-derived override function. It is up to the C++ compiler vendors how they implement it. Mostly, they follow similar approach of using virtual table approach.
Virtual table
Virtual table is a structure created by compiler to store pointers to virtual functions of the class. Each of the class creates it own virtual table. For example, consider the following program:
class Base
{
virtual void f1();
virtual void f2();
};
class Derived: public Base
{
virtual void f1() override;
};Base class virtual table contains pointers to its virtual functions Base:f1 and Base:::f2 which are the most-derived functions.
Derived class virtual table contains pointers to function Derived::f1 as it is the most-derived override and function Base::f2 from the base class as there is no override for it.
So, virtual tables for both of the classes look like below:
Base's virtual table
+-----------+
| Base |
+-----------+
| f1() |
| f2() |
+-----------+
Derived's virtual table
+-----------+
| Derived |
+-----------+
| f1() |
| Base::f2()|
+-----------+
Hidden virtual table pointer
Now, we have virtual table. Compiler also creates an extra member for the base class of type pointer pointing to this virtual table of the class. This gets inherited to the derived class and points to derived class virtual table when derived object gets created.
Let’s say that pointer is *vptr then:
Base object
+----------------+
| vptr ----------+------> Base virtual table
+----------------+
| user members |
+----------------+
Derived object
+----------------+
| vptr ----------+------> Derived virtual table
+----------------+
| Base members |
+----------------+
| Derived members|
+----------------+
Calls
Derived d;
Base* b{&d};
b->f1();Here, derived object is getting constructed where first Base class constructor would be called that will make vptr to point to the base class virtual table. After it the derived class constructor would be called which makes vptr to point to the derived class virtual table. And this is the reason, when we call virtual functions inside the constructor, they do not work as expected (explained in note virtual functions).
When we invoke f1 through the Base* b pointer, compiler can’t determine the object type which b points to. Static type says Base* but actually it is pointing to another type Derived* object. So, it is basically a dynamic type (as b can point to any other derived class object). So, compiler must use virtual dispatch for f1(). Here, compiler generates the code which calls the corrected f1() based on the virtual table and pointer pointing to the table.
In this case, the code that will run at runtime, determines that the pointer is pointing to Derived class virtual table where f1 is Derived::f1() and then the right function gets called.
Efficiency and performance
Figuring out and calling a virtual function through virtual dispatch contains multiple level of indirection. First is to look for the virtual table for the class, second is to figure out the function pointer in the table, third is to use that pointer to call the function.
However, our current CPUs and compilers are good enough that make this indirection unnoticeable.
One more thing to note, as class not contains a hidden virtual table pointer, the size of the created object would also include that pointer size. So, the actual object size would the other members and plus this virtual table pointer.