Date
November, 2018
Dispatch is how the compiler knows which routine to execute at compilation time. However, sometimes the compiler won't know at compilation which routine to execute. In this case, virtual functions are needed. A virtual function is a member function of the base class that is overridden by a derived class.
To start, we'll begin with a simple base class, "Vehicle":
#include <iostream>
using namespace std;
class Vehicle
{
public:
virtual void loadCapacity();
virtual void applyBrakes();
virtual void fuelUp();
};
void Vehicle::loadCapacity() {
cout << “Vehicle is loaded to capacity.” << endl;}
void Vehicle::applyBrakes() {
cout << “Vehicle is braking.” << endl;}
void Vehicle::fuelUp() {
cout << “Vehicle is fueling up.” << endl;}
This base class implements 3 virtual functions, which can then be overridden by subclasses of Vehicle:
class Car : public Vehicle
{
public:
void applyBrakes();
void fuelUp();
};
void Car::applyBrakes() {
cout << “Car is braking.” << endl;}
void Car::fuelUp() {
cout << “Car is fueling up with gas.” << endl;}
Now the subclass Car, which inherits from Vehicle, has its own implementation of the virtual methods (except for loadCapacity()
). Now say at run-time in the main method, this line is called:
Vehicle *v = new Car();
v->fuelUp();
with output:
Car is fueling up with gas.
How are the function calls resolved for our object v? How does it know to call cars fuelUp() function correctly rather than vehicle's?
For both Vehicle and Car classes, a virtual table at the assembly level is created that contains pointers to each definition of the virtual methods, whether it be inherited from a base class or declared within the class. Here are the vtables for our two classes below:
-compiled with x86 gcc assembly
vtable for Vehicle:
.quad 0
.quad typeinfo for Vehicle
.quad Vehicle::loadCapacity()
.quad Vehicle::applyBrakes()
.quad Vehicle::fuelUp()
vtable for Car:
.quad 0
.quad typeinfo for Car
.quad Vehicle::loadCapacity()
.quad Car::applyBrakes()
.quad Car::fuelUp()
You can see that because Car doesn't declare a loadCapacity()
in its own class, it's vtable contains a pointer to Vehicle's declaration of loadCapacity()
. You can see that in addition to the function declarations, .quad 0 is created which makes space for the v-pointer to the table, and also typeinfo is created for each class ( which ends up equating to "7Vehicle" and "3Car" for each as unique identifiers).
When Vehicle *v = new Car();
is called,
v->_vptr.Vehicle;
)v->_vptr.Car)
Here is the assembly for Vehicle *v = new Car();
where you can see these calls
mov edi, 8
call operator new(unsigned long)
mov rbx, rax
mov QWORD PTR [rbx], 0
mov rdi, rbx
call Car::Car() [complete object constructor]
mov QWORD PTR [rbp-24], rbx
And here is the Car constructor which shows the creation of the vtable and an internal Vehicle construction call:
Car::Car() [base object constructor]:
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call Vehicle::Vehicle() [base object constructor]
mov edx, OFFSET FLAT:vtable for Car+16
mov rax, QWORD PTR [rbp-8]
mov QWORD PTR [rax], rdx
nop
leave
ret
Thus, when v->fuelUp();
is called, v points to the v-pointer of the car virtual table (a memory address), a number is added to that address to access the function pointer (16 bytes below as each function pointer is 8 bytes and fuelUp is car's second function declaration), which then is followed to call Car's fuelUp() routine (by calling the address in rax below).
v->fuelUp();
mov rax, QWORD PTR [rbp-24]
mov rax, QWORD PTR [rax]
add rax, 16
mov rax, QWORD PTR [rax]
mov rdx, QWORD PTR [rbp-24]
mov rdi, rdx
call rax