views:

1882

answers:

3

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?

+3  A: 

The extra size is probably because of the extra VTables (http://en.wikipedia.org/wiki/Vtable) allocated by the virtual classes and multiple inheritance.

Paul Betts
+14  A: 

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.

Mark Santesson
Mark, how did you generate that layout?
Charlie Martin
If you mean the table format, it is just code. I just guessed at the contents. I'll edit my answer with more details and reference to the ppinsider's answer.
Mark Santesson
i enjoyed reading it. thanks +1
Johannes Schaub - litb
Impressive Answer mark :)
mahesh
+9  A: 

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.

but if none of the classes have a virtual function, why the vtable?
wilhelmtell
Run the code through g++ with that option and you'll see why. Look at the link I gave to understand it all; much better than me trying to re-hash it here!
what does "base" mean in "base size=16 base align=4"? Does it means all base classes?
ibread