I have spent the past numerous years working on a game engine that avoided the Diamond of Death. This is a phenomena that occurs in languages that support multiple inheritance.
I decided to take a look at how Microsoft Visual C++ implements the solution using virtual inheritance. This is expressed by using “class B : public virtual A”.
A simple characterization is below:
class A { protected: int value; public: A() : value(1) { } virtual void WhereAmI() { printf("I'm in A (value = %d)!\n", value); } }; class B : public virtual A { int bvalue; public: B() : bvalue(2) { value += 2; } virtual void WhereAmI() { printf("I'm in B (value = %d)!\n", value); } }; class C : public virtual A { int cvalue; public: C() : cvalue(4) { value += 4; } virtual void WhereAmI() { printf("I'm in C (value = %d)!\n", value); } }; class D : public B, public C { int dvalue; public: D() : dvalue(8) { } virtual void WhereAmI() { printf("I'm in D (B::value = %d)!\n", B::value); printf("I'm in D (C::value = %d)!\n", C::value); } };
An instance of D will first construct a single instance of A, then B, C, and finally D. The instance of A will be shared between B & C so the final value in A.value will be 7. In fact the output of the print statements in D will both be 7 since the instance of A is shared.
Constructing an instance of D requires a bit more information. The initialization sequence is:
- The vtables for B & C are set up in memory at their respective offsets
- The constructor for A is called
- A’s initial vtable is established
- A’s data is initialized
- The constructor for B is called
- Constructor call to A is skipped
- Initialization state for A is updated
- A’s vtable is updated
- B’s data is initialized
- The constructor for C is called
- Constructor call to A is skipped
- Initialization state for A is updated
- A’s vtable is updated
- C’s data is initialized
- D is constructed
- A’s vtable is changed again
- D’s data is initialized
The layout of D in memory is (based on the above example, 32 bit executable):
Field | Offset |
---|---|
B’s vtable | 0 |
B’s data | +4 |
C’s vtable | +8 |
C’s data | +12 |
D’s data | +16 |
A’s init state | +20 |
A’s vtable | +24 |
A’s data | +28 |
A’s class information follows the data for any class that inherits from it virtually. The vtable entry for A in B’s vtable (in D) has an offset of 24 (0x18) pointing to the start of A’s vtable.
The final in memory representation is:
9c 77 d8 00 02 00 00 00 00 7b d8 00 04 00 00 00 œwØ......{Ø..... 08 00 00 00 00 00 00 00 98 77 d8 00 07 00 00 00 ........˜wØ.....
Even looking at an instance of a B shows the same pattern:
Field | Offset |
---|---|
B’s vtable | 0 |
B’s data | +4 |
A’s init state | +8 |
A’s vtable | +12 |
A’s data | +16 |
With an offset from B’s vtable to A of 12 (0xc).
So you get the benefits of having a single instance of A with the added complexity of additional vtables, initialization state, and having to evaluate the location of A through a vtable to resolve queries against A.
Final question: is it is possible (and is there value) in being able to create custom vtables during execution to point to a virtual instance of a class much farther away in memory? While it would create bloat for duplicating the vtable it might provide interesting functionality/flexibility.