My understanding of vtables is that, if I have a class Cat with a virtual function speak() with subclasses Lion and HouseCat, there is a vtable which maps speak() to the correct implementation for each Subclass. So a call
cat.speak()
Compiles to
cat.vtable[0]()
That is, a look-up in the vtable position 0 and a call of the function pointer in this position.
My question is: What happens on multiple inheritance?
Let's add a class Pet. Pet has virtual functions speak() and eat(). HouseCat extends Pet, while Lion does not. Now, I need to make sure that
pet.eat()
Compiles as
pet.vtable[1]()
That is vtable[0] needs to be speak(). Pet.eat needs to be slot 1. That is because cat.speak() needs to access slot 0 in the vtable, and if, for a HouseCat, slot 0 happens to be eat, this will go horribly wrong.
How does the compiler ensure that the vtable indexes fit together?
The vtable contains an entry for each virtual function accessible by the class and stores a pointer to its definition. Only the most specific function definition callable by the class is stored in the vtable.
The virtual table is a lookup table of functions used to resolve function calls in a dynamic/late binding manner. The virtual table sometimes goes by other names, such as “vtable”, “virtual function table”, “virtual method table”, or “dispatch table”.
V-tables (or virtual tables) are how most C++ implementations do polymorphism. For each concrete implementation of a class, there is a table of function pointers to all the virtual methods. A pointer to this table (called the virtual table) exists as a data member in all the objects.
All classes with a virtual method will have a single vtable that is shared by all objects of the class. Each object instance will have a pointer to that vtable (that's how the vtable is found), typically called a vptr. The compiler implicitly generates code to initialize the vptr in the constructor.
Nothing is set by the spec, but typically the compiler would generate one vtable for every immediate non-virtual base class, plus one vtable for the deriving class - and then the vtable for the first base class and the vtable for the derived class will be merged.
So more concretely, what the compiler generates when constructing the classes:
Cat
[vptr | Cat fields]
[0]: speak()
Pet
[vptr | Pet fields]
[0]: eat()
Lion
[vptr | Cat fields | Lion fields]
[0]: speak()
HouseCat
[vptr | Cat fields | vptr | Pet fields | HouseCat fields]
[0]: speak() [0]: eat()
What the compiler generates on calls / casts (variable name is the static type name):
cat.speak()
obj[0][0]()
- valid for Cat, Lion and the "Cat" part of HouseCatpet.eat()
obj[0][0]()
- valid for Pet and the "Pet" part of HouseCatlion.speak()
obj[0][0]()
- valid for LionhouseCat.speak()
obj[0][0]()
- valid for the "Cat" part of HouseCathouseCat.eat()
obj[Cat size][0]()
- valid for the "Pet" part of HouseCat(Cat)houseCat
obj
(Pet)houseCat
obj + Cat size
So I guess the key thing that confused you is that (1) multiple vtables are possible and (2) upcasts might actually return a different address.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With