Reputation: 12895
The following code prints 20, i.e. sizeof(z) is 20.
#include <iostream.h>
class Base
{
public:
int a;
};
class X:virtual public Base
{
public:
int x;
};
class Y:virtual public Base
{
public:
int y;
};
class Z:public X,public Y
{
};
int main()
{
Z z;
cout << sizeof(z) <<endl;
}
Whereas if I don't use virtual base classes here, i.e. for the following code : sizeof(z) is 16.
#include <iostream.h>
class Base
{
public:
int a;
};
class X:public Base
{
public:
int x;
};
class Y:public Base
{
public:
int y;
};
class Z:public X,public Y
{
};
int main()
{
Z z;
cout << sizeof(z) <<endl;
}
Why is sizeof(z) more(20) in the first case? Shouldn't it be 12, since Base will be included only once in Z?
Upvotes: 12
Views: 4518
Reputation: 9704
Let's look at the class layout of the two cases.
Without the virtual, you have two base classes ("X" and "Y") with an integer each, and each of those classes have integrated into them a "Base" base class which also has an integer. That is 4 integers, 32-bits each, totalling your 16 bytes.
Offset Size Type Scope Name
0 4 int Base a
4 4 int X x
8 4 int Base a
12 4 int Y y
16 size (Z members would come at the end)
(Edit: I've written a program in DJGPP to get the layout and tweaked the table to account for it.)
Now let's talk about virtual base classes: they replace the actual instance of the class with a pointer to a shared instance. Your "Z" class has only one "Base" class, and both instances of "X" and "Y" point to it. Therefore, you have integers in X, Y, and Z, but you only have the one Z. That means you have three integers, or 12 bytes. But X and Y also have a pointer to the shared Z (otherwise they wouldn't know where to find it). On a 32-bit machine two pointers will add an additional 8 bytes. This totals the 20 that you see. The memory layout might look something like this (I haven't verified it... the ARM has an example where the ordering is X, Y, Z, then Base):
Offset Size Type Scope Name Value (sort of)
0 4 Base offset X ? 16 (or ptr to vtable)
4 4 int X x
8 4 Base offset Y ? 16 (or ptr to vtable)
12 4 int Y y
16 4 int Base a
20 size (Z members would come before the Base)
So the memory difference is a combination of two things: one less integer and two more pointers. Contrary to another answer, I don't believe vtables pay any (edit) direct (/edit) roll in this, since there are no virtual functions.
Edit: ppinsider has provided more information on the gcc case, in which he demonstrates that gcc implements the pointer to the virtual base class by making use of an otherwise empty vtable (i.e., no virtual functions). That way, if there were virtual functions, it wouldn't require an additional pointer in the class instance, requiring more memory. I suspect the downside is an additional indirection to get to the base class.
We might expect all compilers to do this, but perhaps not. The ARM page 225 discusses virtual base classes without mentioning vtables. Page 235 specifically addresses "virtual base classes with virtual functions" and has a diagram indicating a memory layout where there are pointers from the X and Y parts that are separate from the pointers to the vtable. I would advise anyone not to take for granted that the pointer to Base will be implemented in terms of a table.
Upvotes: 20
Reputation: 487
Mark Santesson's answer is pretty much on the money, but the assertation that there are no vtables is incorrect. You can use g++ -fdump-class-hierarchy to show what's going on. Here's the no virtuals case:
Class Base
size=4 align=4
base size=4 base align=4
Base (0x19a8400) 0
Class X
size=8 align=4
base size=8 base align=4
X (0x19a8440) 0
Base (0x19a8480) 0
Class Y
size=8 align=4
base size=8 base align=4
Y (0x19a84c0) 0
Base (0x19a8500) 0
Class Z
size=16 align=4
base size=16 base align=4
Z (0x19b1800) 0
X (0x19a8540) 0
Base (0x19a8580) 0
Y (0x19a85c0) 8
Base (0x19a8600) 8
Pay particular attention to the "base size" argument. Now the virtuals case, and showing only Z:
Class Z
size=20 align=4
base size=16 base align=4
Z (0x19b3000) 0
vptridx=0u vptr=((& Z::_ZTV1Z) + 12u)
X (0x19a8840) 0
primary-for Z (0x19b3000)
subvttidx=4u
Base (0x19a8880) 16 virtual
vbaseoffset=-0x0000000000000000c
Y (0x19a88c0) 8
subvttidx=8u vptridx=12u vptr=((& Z::_ZTV1Z) + 24u)
Base (0x19a8880) alternative-path
Note the "base size" is the same, but the "size" is one pointer more, and note that there is now a vtable pointer! This in turn contains the construction vtables for the parent classes, and all the inter-class magic (construction vtables, and virtual table table (VTT)), as described here:
http://www.cse.wustl.edu/~mdeters/seminar/fall2005/mi.html
Note that the actual function dispatch vtable will be empty.
Upvotes: 9
Reputation: 74654
The extra size is probably because of the extra VTables (http://en.wikipedia.org/wiki/Vtable) allocated by the virtual classes and multiple inheritance.
Upvotes: 3