C ++ Multiple inheritance memory layout with "Empty classes"

I know that the memory layout of multiple inheritance is not defined, so I should not rely on it. However, I can rely on him in a special case. That is, a class has only one β€œreal” superclass. All the rest are "empty classes", i.e. Classes that have neither fields nor virtual methods (i.e., they have only non-virtual methods). In this case, these additional classes should not add anything to the class’s memory layout. (More concisely, in C ++ 11, the class has a standard layout)

Can we conclude that all superclasses will not have any bias? For example:.

#include <iostream> class X{ int a; int b; }; class I{}; class J{}; class Y : public I, public X, public J{}; int main(){ Y* y = new Y(); X* x = y; I* i = y; J* j = y; std::cout << sizeof(Y) << std::endl << y << std::endl << x << std::endl << i << std::endl << j << std::endl; } 

Here Y is a class with X , which is the only real base class. The output of the program (when compiling on linux with g ++ 4.6) is as follows:

-

0x233f010

0x233f010

0x233f010

0x233f010

As I said, there is no pointer adjustment. But this implementation is specific or I can rely on it. If I get an object of type I (and I know that only these classes exist), can I use reinterpret_cast to add it to X ?

My hopes are that I can rely on it because the specification says that the size of the object should be at least byte. Therefore, the compiler cannot select a different layout. If it were I and J behind the members of X , then their size would be zero (because they have no members). Therefore, the only reasonable choice is to align all superclasses without bias.

Am I playing with fire correctly if I use reinterpret_cast from I to X here?

+7
source share
1 answer

In C ++ 11, the compiler should use empty base class optimization for standard layout types. see stack overflow.

In your specific example, all types are standard layout classes and do not have common base classes or members (see below), so you can rely on this behavior in C ++ 11 (and in practice, I think many compilers have already followed this the rule, of course, is g ++, while others are Itanium C ++ ABI .)

Caution: make sure you do not have base classes of the same type, because they must be at different addresses, for example.

 struct I {}; struct J : I {}; struct K : I { }; struct X { int i; }; struct Y : J, K, X { }; #include <iostream> Y y; int main() { std::cout << &y << ' ' << &y.i << ' ' << (X*)&y << ' ' << (I*)(J*)&y << ' ' << (I*)(K*)&y << '\n'; } 

prints:

 0x600d60 0x600d60 0x600d60 0x600d60 0x600d61 

For type Y only one of the bases I can be at zero offset, therefore, if the sub-object X is at zero offset (i.e. offsetof(Y, i) is zero), and one of I based on the same address, but another I base is (at least with g ++ and Clang ++) one byte per object, so if you received I* you could not reinterpret_cast before X* because you don't know which <sub-object I it is indicated I with offset 0 or I with offset 1.

OK, so that the compiler puts the second object I at offset 1 (i.e. inside int ), because I does not have non-static data elements, so you cannot actually dereference or access anything at this address, just get a pointer to the object at this address. If you added non-static data members to I , then Y will no longer be the standard layout and it will not need to use EBO, and offsetof(Y, i) will no longer be zero.

+8
source

All Articles