In order to discover the layout of a polymorphic object, you can compare pointers to member objects; the simple demonstration program "draws" the layout of an object with symbols:
- a lowercase letter is the name of a data member
- an uppercase letter is the name of a base class
*
indicates part of the object that do not belong to any member subobject or base class subobject
There is a symbol for each byte (a char
is a byte by definition).
The vptr(s) must be in the "empty" space, the space not allocated for data members.
Type definitions are:
struct T {
virtual void foo();
int i;
};
struct U {
virtual void bar();
long long l;
};
struct Der : T, U {
};
struct Der2 : virtual T, U {
};
struct Der3 : virtual T, virtual U {
};
Output is:
sizeof void* is 4
sizeof T is 8
sizeof i is 4
i is at offset 4
layout of T is
****iiii
sizeof U is 12
sizeof U::l is 8
l is at offset 4
layout of U is
****llllllll
sizeof Der is 20
Der::i is at offset 4
Der::l is at offset 12
Der::T is at offset 0
Der::U is at offset 8
layout of Der is
TTTTiiiiUUUUllllllll
sizeof Der2 is 20
Der2::i is at offset 16
Der2::l is at offset 4
Der2::T is at offset 12
Der2::U is at offset 0
layout of Der2 is
UUUUllllllllTTTTiiii
sizeof Der3 is 24
Der3::i is at offset 8
Der3::l is at offset 16
Der3::T is at offset 4
Der3::U is at offset 12
layout of Der3 is
****TTTTiiiiUUUUllllllll
See https://ideone.com/g5SZwk
Because we know the compiler is using vptrs, the locations of the vptrs are obvious in these "drawings".
About inheritance in C++
Non-virtual inheritance
When virtual inheritance is not used, the base class subobjects inheritance graph is always a tree rooted in the most derived class, even when the subtyping graph is not a tree:
struct Repeated {
virtual void f();
virtual void g();
};
struct Left : Repeated {
void g();
};
struct Right : Repeated {
void g();
};
struct Bottom : Left, Right {
void f();
};
The subtyping graph is:
Left
/ \
Repeated Bottom
\ /
Right
The subobject graph is:
Left::Repeated --- Left
\
Bottom
/
Right::Repeated --- Right
This is crucial effect of non-virtual inheritance: the graphs don't always match. If you don't understand that you don't understand non-virtual inheritance!
This implies that conversions from Bottom*
to Repeated*
are ambiguous.
In this example:
Bottom::f()
overrides both Left::Repeated::f()
and Right::Repeated::f()
at the same time.
Left::Repeated::g()
is overridden by Left::g()
Right::Repeated::g()
is overridden by Right::g()
Here the lookup of the name g
in Bottom
would fail with an ambiguity, so it would be an error to use an unqualified g
in Bottom
.
Virtual inheritance
When virtual inheritance is used, the base class subobjects inheritance is an acyclic directed graph with the most derived class as a unique terminal:
struct Unique { virtual void f(); };
struct Left : virtual Unique { void f(); };
struct Right : virtual Unique { void f(); };
struct Bottom : Left, Right { void f(); };
Here all other f()
declarations override Unique::f()
.
Here the subobject graph matches the subtype graph:
Left
/ \
Unique Bottom
\ /
Right