I have been learning some more "indepth" things about virtual tables recently and this question came to my mind.
Suppose we have this sample:
class A {
virtual void foo();
}
class B : public A {
void foo();
}
In this case from what I know there will be a vtable present for each class and the dispatch would be quite simple.
Now suppose we change the B class to something like this:
class B : public C, public A {
void foo();
}
If the class C has some virtual methods the dispatch mechanism for B will be more complicated. There will probably be 2 vtables for both inheritance paths B-C, B-A etc.
From what I've learned so far it seems that if there would be somewhere else in the codebase function like this:
void bar(A * a) {
a->foo();
}
It would need to compile now with the more complicated dispatch mechanism because at compile time we do not know if "a" is pointer to A or B.
Now to the question. Suppose we added the new class B to our codebase. It doesn't seem likely to me that it would require to recompile the code everywhere where the pointer to A is used.
From what I know the vtables are created by the compiler. However is it possible that this fixup is solved by the linker possibly during relocation? It does seem likely to me I just can not find any evidence to be sure and therefore go to sleep right now :)
Inside void bar(A * a)
, the pointer is definitely to an A
object. That A
object may be a subobject of something else like a B
, but that's irrelevant. The A
is self-contained and has its own vtable pointer which links to foo
.
When the conversion from B *
to A *
occurs, such as when bar
is called with a B *
, a constant offset may be added to the B *
to make it point to the A
subobject. If the first thing in every object is the vtable, then this will also set the pointer to the A
vtable as well. For single inheritance, the adjustment is unnecessary.
Here is what memory looks like for a typical implementation:
| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
B vt: | ptrs to methods of C (with B overrides) | ptrs to methods of B |
AB vt: | ptrs to methods of A (with B overrides) |
(Note that typically the AB vt
is really still part of the B vt
; they would be contiguous in memory. And ptrs to methods of B
could then go after ptrs to methods of A
. I just wrote it this way for formatting clarity.)
When you convert a B *
to an A *
, you go from this:
| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
^ your B * pointer value
to this:
| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
^ your A * pointer value
Using a static_cast
from A *
to B *
will move the pointer backwards, in the other direction.