0

I know that in case of virtual inheritance, a vptr is needed to access the base class members, so I looked at a program that involved virtual inheritance but I was amazed at the size of the class. The code is below:

#include<iostream>
using namespace std;

class Base
{
    public:
};

class D1:public Base
{
};

class D2:virtual public Base
{
};

class DD: public D1, public D2
{
};

int main()
{
    DD cObj;
    cout<<sizeof(cObj)<<endl;
    return 0;
}

For size of pointers = 4,
The size of class DD comes to be 8, even though class D1 doesn't use the usual Dreaded Diamond virtual inheritance.
If I make D1 use virtual inheritance the size comes to be 8 as well - which is OK(two vptr one for D1 and other for D2). But why 8 even without it?
Please can someone explain the reason behind it.
Thanks in advance.

Edited by tapananand

2
Contributors
1
Reply
25
Views
2 Years
Discussion Span
Last Post by mike_2000_17
4

If you print out all the class sizes, things will become a bit clearer. If I run the following:

#include<iostream>
using namespace std;

class Base {};

class D1 : public Base {};

class D2 : virtual public Base {};

class DD : public D1, public D2 {};

int main()
{
    cout<<sizeof(void*)<<endl;
    cout<<sizeof(Base)<<endl;
    cout<<sizeof(D1)<<endl;
    cout<<sizeof(D2)<<endl;
    cout<<sizeof(DD)<<endl;
    return 0;
}

I get as output:

4
1
1
4
8

So, for a pointer size of 4, the Base class has a trivial size of 1 (because the standard says it cannot be 0), the D1 class has the same size as Base because that's all it contains and there is no virtual inheritance there. Then, the D2 class has the size of 4 because it needs to contain a pointer to the virtual base class, and that virtual base class' storage is absorbed in the D2 class (empty base-class optimization). And finally, the DD class has a size of 8 because it is composed of a subobject of class D1 and a subobject of class D2. The natural memory alignment of the platform is 32bit (4 bytes), and therefore, the compiler will optimize the memory layout such that variables (data members, incl. virtual pointers) fall on 4 byte alignment boundaries. So, the first 4 bytes of memory are used by the D1 subobject, where only really 1 byte is needed while the other 3 bytes are padding (unused bytes), and the last 4 bytes are used by the D2 subobject, which uses all 4 of these bytes.

If you change the inheritance in D1 to use virtual inheritance, you will get a sizeof(D1) == 4, but the size of the DD class will remain at 8. It's just that now, there won't be any padding because all the bytes are used.

Also, if you change the order of D1 and D2, so that D2 comes as the first base-class of the DD class, you still get 8 bytes as the size of DD class. This time, you would have: 4 bytes for D2 class, 1 byte for D1 class, and 3 bytes of padding. The reason why the size is not 5 bytes (eliminating the padding at the end) is because if you create an array of DD objects, you want (for good performance) each D2 subobject to fall on the 4 byte alignment boundary. And if you put DD objects next to each other in memory, the total size needs to be a multiple of 4 bytes in order to get that favorable alignment for all objects in that array.

The last issue you might raise is the fact that D1, without virtual inheritance, and with an empty base class, is an empty class, and so, why can it not be absorbed into DD, i.e., leaving DD with a size of 4. Now, this would be true if only there was no common base class. The standard requires that if there are common non-virtual base classes between classes in the inheritance tree, then these base-class instances must have distinct addresses. This means that the Base class subobject inherited from D1 must have a distinct address from the Base class subobject inherited from D2, because these are distinct subobjects. If the compiler were to merge D1 (which is empty) into DD, by empty base-class optimization, it would end up having the same address as the D2 subobject within DD, and that's not allowed. If you remove the base-class for the D1 class, then you get a size for DD of 4 bytes, because the D1 subobject gets merged into the DD class since it requires no memory. This rule can be a bit tricky, and compilers usually go with the most conservative assumption, meaning that as soon as there is a common base-class between an empty class and some other class in the inheritance tree, it will essentially disable empty base-class optimization for that class.

I think this covers pretty much every angle.

Votes + Comments
Awesome Answer!! Thanks a lot!!
This question has already been answered. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.