Sunday, November 2, 2008

c++: virtual inheritance

Virtual inheritance is an important thing when we are talking about multiply inheritance.
Basically you can find term 'virtual base class' which means that the base class that is met in inheritance tree is shared between derived classes.
Let's look on the inheritance tree w/o virtual base class

class A
{
};
class B : public A
{
};
class D : public A
{
};
class E : public B, public D
{
};
Class E will have 2 copies of A(derived from B and D). To be more concrete let's look at the output of -fdump-class-hierarchy g++ option
Class A
   size=1 align=1
   base size=0 base align=1
A (0xb7f32680) 0 empty

Class B
   size=1 align=1
   base size=1 base align=1
B (0xb7f326c0) 0 empty
  A (0xb7f32700) 0 empty

Class D
   size=1 align=1
   base size=1 base align=1
D (0xb7f32740) 0 empty
  A (0xb7f32780) 0 empty

Class E
   size=2 align=1
   base size=2 base align=1
E (0xb7f327c0) 0 empty
  B (0xb7f32800) 0 empty
    A (0xb7f32840) 0 empty
  D (0xb7f32880) 1 empty
    A (0xb7f328c0) 1 empty
Indeed, the most obvious is the overhead: E contains 2 instances of A(by addresses 0xb7f32840 and 0xb7f328c0).
The other thing you are unable to call methods of A from E directly. There is no distinct path from E to A. The following code wouldn't be compiled. The compiler will raise an error that reference to methodA is ambiguous.
class A
{
    public:
        virtual void methodA(){}
};
class B : public A
{
};
class D : public A
{
};
class E : public B, public D
{
    virtual void methodE(){ methodA(); }
};
In this case you should explicitly call methodA either from B or D
virtual void methodE(){ B::methodA(); D::methodA(); }
Also you can face a problem with A as a base of E
A *a = new E;//‘A’ is an ambiguous base of ‘E’
You say you can do smth like this
A *a; 
    E *e = new E;
    void *v = (void *)e;
    a = (A *)v;
No chance to expect defined behavior with this piece of c-ish code.

Now let's look how things change w/ virtual base class.
class A
{
};
class B : virtual public A
{
};
class D : virtual public A
{
};
class E : public B, public D
{
};
Class A was defined as a virtual base class in the code above. Let's look what g++ says
Class A
   size=1 align=1
   base size=0 base align=1
A (0xb7f7a680) 0 empty

Class B
   size=4 align=4
   base size=4 base align=4
B (0xb7f7a6c0) 0 nearly-empty
    vptridx=0u vptr=((& B::_ZTV1B) + 12u)
  A (0xb7f7a700) 0 empty virtual
      vbaseoffset=-0x00000000c

Class D
   size=4 align=4
   base size=4 base align=4
D (0xb7f7a7c0) 0 nearly-empty
    vptridx=0u vptr=((& D::_ZTV1D) + 12u)
  A (0xb7f7a800) 0 empty virtual
      vbaseoffset=-0x00000000c

Class E
   size=8 align=4
   base size=8 base align=4
E (0xb7f7a880) 0
    vptridx=0u vptr=((& E::_ZTV1E) + 12u)
  B (0xb7f7a8c0) 0 nearly-empty
      primary-for E (0xb7f7a880)
      subvttidx=4u
    A (0xb7f7a900) 0 empty virtual
        vbaseoffset=-0x00000000c
  D (0xb7f7a940) 4 nearly-empty
      subvttidx=8u vptridx=12u vptr=((& E::_ZTV1E) + 24u)
    A (0xb7f7a900) alternative-path
Class E has one instance of A(by address 0xb7f7a900). We got rid of overhead. In the output you can see that there is only one path from A to E. There is no problem with compiling the following code
class E : public B, public D
{
    virtual void methodE(){ methodA(); }
};
And A can be used as a base class for E
A *a = new E;
With virtual inheritance we achieve better object model but we can loose some performance in order to run-time resolving paths to base from derived and from base to derived classes through v-table. With small classes we can even get overhead if v-table is pretty big.

The other thing you should be aware that c-style casting between derived and base classes(both ways) may break your program. Use dynamic_cast instead. That is because with the v-table classes not more of POD(Plain Old Data) types. c-style casts won't work correctly with non-POD types.

4 comments:

j_s said...

thank you for explanation, very helpfull.

Ni@m said...

You are welcome!
I'm glad you found out this interesting!

priyanka said...

I have a question related to this post.
Size of an empty derived class, that has a virtual base class comes out to be 4. why? Does virtual inheritance creates a virtual table?.
class A {};
class B: public virtual A {}

sizeof class b is 4.

Ni@m said...

Hi, priyanka.

Yes, this is due to the presence of virtual table.

class B now contains pointer to vtable.