Sometimes, you just really need to see some code / diagrams :) Note that there is no mention of this implementation detail in the Standard.
First of all, let's see how to implement methods in C++:
struct Base
{
void foo();
};
This is similar to:
struct Base {};
void Base_foo(Base& b);
And in fact, when you look at a method call within a debugger, you'll often see the this
argument as the first parameter. It is sometimes called an implicit parameter.
Now, on to the virtual table. In C and C++ it is possible to have pointers to function. A vtable is essentially a table of pointers to functions:
struct Base
{
int a;
};
void Base_set(Base& b, int i) { b.a = i; }
int Base_get(Base const& b) { return b.a; }
struct BaseVTable
{
typedef void (*setter_t)(Base&, int);
typedef int (*getter_t)(Base const&);
setter_t mSetter;
getter_t mGetter;
BaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gBaseVTable(&Base_set, &Base_get);
Now I can do something like:
void func()
{
Base b;
(*gBaseVTable.mSetter)(b, 3);
std::cout << (*gBaseVTable.mGetter)(b) << std::endl; // print 3
}
Now, on to the inheritance. Let's create another structure
struct Derived: Base {}; // yeah, Base does not have a virtual destructor... shh
void Derived_set(Derived& d, int i) { d.a = i+1; }
struct DerivedBaseVTable
{
typedef void (*setter_t)(Derived&,int);
typedef BaseVTable::getter_t getter_t;
setter_t mSetter;
getter_t mGetter;
DerivedBaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
} gDerivedBaseVTable(&Derived_set, &Base_get);
And the use:
void func()
{
Derived d;
(*gDerivedBaseVTable.mSetter)(d, 3);
std::cout << (*gDerivedBaseVTable.mGetter)(d) << std::endl; // print 4
}
But how to automate this ?
- you only need one instance of a vtable per class having at least one virtual function
- each instance of the class will contain a pointer to the vtable as its first attribute (even though you can't really access it by yourself)
Now, what happens in case of multi-inheritance ? Well, inheritance is very much like composition in term of memory layout:
| Derived |
| BaseA | BaseB |
| vpointer | field1 | field2 | padding? | vpointer | field1 | field2 | padding? |
There will thus be 2 virtual tables for MostDerived
: one to change the methods from BaseA
and one to change the methods from BaseB
.
Pure virtual functions are generally represented as a null pointer (simply) in the corresponding field.
And finally, construction and destruction:
Construction
BaseA
is constructed: first the vpointer is initialized, then the attributes, then the body of the constructor is executed
BaseB
is constructed: vpointer, attributes, body
Derived
is constructed: replace the vpointers (both), attributes, body
Destruction
Derived is destructed
: body of the destructor, destroy attributes, put the base vpointers back
BaseB
is destructed: body, attributes
BaseA
is destructed: body, attributes
I think it's pretty comprehensive, I'd be glad if some C++ gurus around there could review this and check I haven't made any stupid mistake. Also, if something is missing, I'd be glad to add it.