Search code examples
c++virtual-functionsvirtual-inheritancememory-layout

Why does virtual keyword increase the size of derived a class?


I have two classes - one base class and one derived from it :

class base {

 int i ;

  public :
  virtual ~ base () { }
};

class derived :  virtual public base { int j ; };

main()

{ cout << sizeof ( derived ) ; }

Here the answer is 16. But if I do instead a non-virtual public inheritance or make the base class non-polymorphic , then I get the answer as 12, i.e. if I do :

class base {

 int i ;

 public :
virtual ~ base () { }
};

class derived :  public base { int j ; };

main()

{ cout << sizeof ( derived ) ; }

OR

class base {

int i ;

public :
~ base () { }
};

class derived :  virtual public base { int j ; };

main()

{ cout << sizeof ( derived ) ; }

In both the cases answer is 12.

Can someone please explain why there is a difference in the size of the derived class in 1st and the other 2 cases ?

( I work on code::blocks 10.05, if someone really need this )


Solution

  • The point of virtual inheritance is to allow sharing of base classes. Here's the problem:

    struct base { int member; virtual void method() {} };
    struct derived0 : base { int d0; };
    struct derived1 : base { int d1; };
    struct join : derived0, derived1 {};
    join j;
    j.method();
    j.member;
    (base *)j;
    dynamic_cast<base *>(j);
    

    The last 4 lines are all ambiguous. You have to explicitly whether you want the base inside the derived0, or the base inside derived1.

    If you change the second and third line as follows, the problem goes away:

    struct derived0 : virtual base { int d0; };
    struct derived1 : virtual base { int d1; };
    

    Your j object now only has one copy of base, not two, so the last 4 lines stop being ambiguous.

    But think about how that has to be implemented. Normally, in a derived0, the d0 comes right after the m, and in a derived1, the d1 comes right after the m. But with virtual inheritance, they both share the same m, so you can't have both d0 and d1 come right after it. So you're going to need some form of extra indirection. That's where the extra pointer comes from.

    If you want to know exactly what the layout is, it depends on your target platform and compiler. Just "gcc" isn't enough. But for many modern non-Windows targets, the answer is defined by the Itanium C++ ABI, which is documented at http://mentorembedded.github.com/cxx-abi/abi.html#vtable.