Memory Layout for Multiple and Virtual Inheritance
Memory Layout for Multiple and Virtual InheritanceWarning. This article is rather technical and assumes a good knowledge of C++ and some assembly language.
In this article we explain the object layout implemented by gcc for multiple and virtual inheritance. Although in an ideal world C++ programmers should not need to know these details of the compiler internals, unfortunately the way multiple (and especially virtual) inheritance is implemented has various non-obvious consequences for writing C++ code (in particular, for downcasting pointers, using pointers to pointers, and the invocation order of constructors for virtual bases). If you understand how multiple inheritance is implemented, you will be able anticipate these consequences and deal with them in your code. Also, it is useful to understand the cost of using virtual inheritance if you care about efficiency. Finally, it is interesting :-) Multiple InheritanceFirst we consider the relatively simple case of (non-virtual) multiple inheritance. Consider the following C++ class hierarchy. class Top { public: int a; }; class Left : public Top { public: int b; }; class Right : public Top { public: int c; }; class Bottom : public Left, public Right { public: int d; }; Using a UML diagram, we can represent this hierarchy as Note that How are
Note that the first attribute is the attribute inherited from Left* left = new Left(); Top* top = left;
Now what happens when we upcast a Bottom* bottom = new Bottom(); Left* left = bottom; This works out nicely. Because of the memory layout, we can treat an object of type Right* right = bottom; For this to work, we have to adjust the pointer value to make it point to the corresponding section of the
After this adjustment, we can access Top* top = bottom; Right, nothing at all. This statement is ambiguous: the compiler will complain error: `Top' is an ambiguous base of `Bottom' The two possibilities can be disambiguated using Top* topL = (Left*) bottom; Top* topR = (Right*) bottom; After these two assignments, Virtual InheritanceTo avoid the repeated inheritance of class Top { public: int a; }; class Left : virtual public Top { public: int b; }; class Right : virtual public Top { public: int c; }; class Bottom : public Left, public Right { public: int d; }; This yields the following hierarchy (which is perhaps what you expected in the first place) while this may seem more obvious and simpler from a programmer's point of view, from the compiler's point of view, this is vastly more complicated. Consider the layout of
The advantage of this layout is that the first part of the layout collides with the layout of Right* right = bottom; Which address do we assign to The solution is non-trivial. We will show the solution first and then explain it. You should note two things in this diagram. First, the order of the fields is completely different (in fact, it is approximately the reverse). Second, there are these new The Bottom* bottom = new Bottom(); Left* left = bottom; int p = left->a; The second assignment makes movl left, %eax # %eax = left movl (%eax), %eax # %eax = left.vptr.Left movl (%eax), %eax # %eax = virtual base offset addl left, %eax # %eax = left + virtual base offset movl (%eax), %eax # %eax = left.a movl %eax, p # p = left.a In words, we use With this setup, we can access the Bottom* bottom = new Bottom(); Right* right = bottom; int p = right->a;
The assignment to Of course, the point of the exercise was to be able to access real Now we can access a DowncastingAs we have seen, casting a pointer of type Suppose we extend our inheritance hierarchy with the following class. class AnotherBottom : public Left, public Right { public: int e; int f; }; The hierarchy now looks like Now consider the following code. Bottom* bottom1 = new Bottom(); AnotherBottom* bottom2 = new AnotherBottom(); Top* top1 = bottom1; Top* top2 = bottom2; Left* left = static_cast<Left*>(top1); The following diagram shows the layout of
Now consider how to implement the static cast from error: cannot convert from base `Top' to derived type `Left' via virtual base `Top' Since we need runtime information, we need to use a dynamic cast instead: Left* left = dynamic_cast<Left*>(top1); However, the compiler is still unhappy: error: cannot dynamic_cast `top' (of type `class Top*') to type `class Left*' (source type is not polymorphic) The problem is that a dynamic cast (as well as use of class Top
{
public:
virtual ~Top() {}
int a;
};
This change necessitates a (Of course, the other classes get a similar new left = __dynamic_cast(top1, typeinfo_for_Top, typeinfo_for_Left, -1); This function Concluding RemarksFinally, we tie a couple of loose ends.(In)variance of Double PointersThis is were it gets slightly confusing, although it is rather obvious when you give it some thought. We consider an example. Assume the class hierarchy presented in the last section (Downcasting). We have seen previously what the effect is of Bottom* b = new Bottom(); Right* r = b; (the value of Bottom** bb = &b; Right** rr = bb; Should the compiler accept this? A quick test will show that the compiler will complain: error: invalid conversion from `Bottom**' to `Right**' Why? Suppose the compiler would accept the assignment of So, *rr = b; This is essentially the same assignment as the assignment to This is correct as long as we access the So, in summary, even if Constructors of Virtual BasesThe compiler must guarantee that all virtual pointers of an object are properly initialised. In particular, it guarantees that the constructor for all virtual bases of a class get invoked, and get invoked only once. If you don't explicitly call the constructors of your virtual superclasses (independent of how far up the tree they are), the compiler will automatically insert a call to their default constructors. This can lead to some unexpected results. Consider the same class hierarchy again we have been considering so far, extended with constructors: class Top { public: Top() { a = -1; } Top(int _a) { a = _a; } int a; }; class Left : public Top { public: Left() { b = -2; } Left(int _a, int _b) : Top(_a) { b = _b; } int b; }; class Right : public Top { public: Right() { c = -3; } Right(int _a, int _c) : Top(_a) { c = _c; } int c; }; class Bottom : public Left, public Right { public: Bottom() { d = -4; } Bottom(int _a, int _b, int _c, int _d) : Left(_a, _b), Right(_a, _c) { d = _d; } int d; }; (We consider the non-virtual case first.) What would you expect this to output: Bottom bottom(1,2,3,4); printf("%d %d %d %d %d\n", bottom.Left::a, bottom.Right::a, bottom.b, bottom.c, bottom.d); You would probably expect (and get) 1 1 2 3 4 However, now consider the virtual case (where we inherit virtually from -1 -1 2 3 4 Why? If you trace the execution of the constructors, you will find Top::Top() Left::Left(1,2) Right::Right(1,3) Bottom::Bottom(1,2,3,4) As explained above, the compiler has inserted a call to the default constructor in To avoid this situation, you should explicitly call the constructor of your virtual base(s): Bottom(int _a, int _b, int _c, int _d): Top(_a), Left(_a,_b), Right(_a,_c) { d = _d; } Pointer EquivalenceOnce again assuming the same (virtual) class hierarchy, would you expect this to print “Equal”? Bottom* b = new Bottom(); Right* r = b; if(r == b) printf("Equal!\n"); Bear in mind that the two addresses are not actually equal ( Casting to
|
reference : https://web.archive.org/web/20160413064252/http://www.phpcompiler.org/articles/virtualinheritance.html