I know the memory layout of multiple inheritance is not defined, so I should not rely on it. However, can I rely on it in a special case. That is, a class has only one "real" super class. All others are "empty classes", i.e., classes that neither have fields nor virtual methods (i.e. they only have non-virtual methods). In this case, these additional classes should not add anything to the memory layout of the class. (More concisely, in the C++11 wording, the class has standard-layout)
Can I infer that all the superclasses will have no offset? E.g.:
#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 the class with X
being the only real base class. The output of the program (when compiled on linux with g++4.6) is as follows:
8
0x233f010
0x233f010
0x233f010
0x233f010
As I concluded, there is no pointer adjustment. But is this implementation specific or can I rely on it. I.e., if I receive an object of type I
(and I know only these classes exist), can I use a reinterpret_cast
to cast it to X
?
My hopes are that that I could rely on it because the spec says that the size of an object must at least be a byte. Therefore, the compiler cannot choose another layout. If it would layout 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 super classes without offset.
Am I correct or am I playing with the fire if I use reinterpret_cast from I
to X
here?
Instead, if classes B and C inherit virtually from class A , then objects of class D will contain only one set of the member variables from class A . This feature is most useful for multiple inheritance, as it makes the virtual base a common subobject for the deriving class and all classes that are derived from it.
Diagram of the Multiple Inheritance In the above diagram, there are two-parent classes: Base Class 1 and Base Class 2, whereas there is only one Child Class. The Child Class acquires all features from both Base class 1 and Base class 2. Therefore, we termed the type of Inheritance as Multiple Inheritance.
Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. The constructors of inherited classes are called in the same order in which they are inherited. For example, in the following program, B's constructor is called before A's constructor.
Master C and Embedded C Programming- Learn as you go So the class can inherit features from multiple base classes using multiple inheritance. This is an important feature of object oriented programming languages such as C++.
In C++11 the compiler is required to use the Empty Base-class Optimization for standard layout types. see https://stackoverflow.com/a/10789707/981959
For your specific example all the types are standard layout classes and don't have common base classes or members (see below) so you can rely on that behaviour in C++11 (and in practice, I think many compilers already followed that rule, certainly G++ did, and others following the Itanium C++ ABI.)
A caveat: make sure you don't have any base classes of the same type, because they must be at distinct addresses, e.g.
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 the type Y
only one of the I
bases can be at offset zero, so although the X
sub-object is at offset zero (i.e. offsetof(Y, i)
is zero) and one of the I
bases is at the same address, but the other I
base is (at least with G++ and Clang++) one byte into the object, so if you got an I*
you couldn't reinterpret_cast
to X*
because you wouldn't know which I
sub-object it pointed to, the I
at offset 0 or the I
at offset 1.
It's OK for the compiler to put the second I
sub-object at offset 1 (i.e. inside the int
) because I
has no non-static data members, so you can't actually dereference or access anything at that address, only get a pointer to the object at that address. If you added non-static data members to I
then Y
would no longer be standard layout and would not have to use the EBO, and offsetof(Y, i)
would no longer be zero.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With