Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does virtual inheritance need to be specified in the middle of a diamond hierarchy?

I have diamond hierarchy of classes:

    A
  /   \
 B     C
  \   /
    D

To avoid two copies of A in D, we need to use virtual inheritance at B and C.

class A                     {  }; 
class B: virtual public A {};
class C: virtual public A   { }; 
class D: public B, public C { }; 

Question: Why does virtual inheritance needs to be performed at B and C, even though the ambiguity is at D? It would have been more intuitive if it is at D.

Why is this feature designed like this by standards committee? What can we do if B and C classes are coming from 3rd party library ?

EDIT: My answer was to indicate B and C classes that they should not invoke A's constructor whenever its derived object gets created, as it will be invoked by D.

like image 348
bjskishore123 Avatar asked Mar 02 '11 10:03

bjskishore123


3 Answers

I'm not sure of the exact reason they chose to design virtual inheritance this way, but I believe the reason has to do with object layout.

Suppose that C++ was designed in a way where to resolve the diamond problem, you would virtually inherit B and C in D rather than virtually inheriting A in B and C. Now, what would the object layout for B and C be? Well, if no one ever tries to virtually inherit from them, then they'd each have their own copy of A and could use the standard, optimized layout where B and C each have an A at their base. However, if someone does virtually inherit from either B or C, then the object layout would have to be different because the two would have to share their copy of A.

The problem with this is that when the compiler first sees B and C, it can't know if anyone is going to be inheriting from them. Consequently, the compiler would have to fall back on the slower version of inheritance used in virtual inheritance rather than the more optimized version of inheritance that is turned on by default. This violates the C++ principle of "don't pay what you don't use for," (the zero-overhead principle) where you only pay for language features you explicitly use.

like image 126
templatetypedef Avatar answered Nov 07 '22 05:11

templatetypedef


Why does virtual inheritance needs to be performed at B and C, even though the ambiguity is at D? It would have been more intuitive if it is at D.

In your example, B and C are using virtual specifically to ask the compiler to ensure there's only one copy of A involved. If they didn't do this, they're effectively saying "I need my own A base class, I'm not expecting to share it with any other derived object". This could be crucial.

Example of not wanting to share a virtual base class

If A was some kind of container, B was derived from it and stored some particular type of object - say "Bat", while C stores "Cat". If D expects to have B and C independently providing information on a population of Bats and Cats they'd be very surprised if a C operation did something to/with the Bats, or a B operation did something to/with the Cats.

Example of wanting to share a virtual base class

Say D needs to provide access to some functions or data members that are in A, say "A::x"... if A is inherited independently (non-virtually) by B and C, then the compiler can't resolve D::x to B::x or C::x without the programmer having to explicitly disambiguate it. This means D can't be used as an A despite having not one but two "is-a" relationships implied by the derivation chain (i.e. if B "is a" A, and D "is a" B, then the user may expect/need to use D as if D "is a" A).

Why is this feature designed like this by standards committee?

virtual inheritance exists because it's sometimes useful. It's specified by B and C, rather than D, because it's an intrusive concept in terms of the design of B and C, and also has implications for the encapsulation, memory layout, construction and destruction and function dispatch of B and C.

What can we do if B and C classes are coming from 3rd party library ?

If D needs to inherit from both and provide access to an A, but B and C weren't designed to use virtual inheritance and can't be changed, then D must take responsibility for forwarding any requests matching the A API to either B and/or C and/or optionally another A it directly inherits from (if it needs a visible "is A" relationship). That might be practical if the calling code knows it's dealing with a D (even if via templating), but operations on the object via pointers to the base classes will not know about the management D is attempting to perform, and the whole thing may be very tricky to get right. But it's a bit like saying "what if I need a vector and I've only got a list", "a saw and not a screwdriver"... well, make the most of it or get what you really need.

EDIT: My answer was to indicate B and C classes that they should not invoke A's constructor whenever its derived object gets created, as it will be invoked by D.

That's an important aspect of this, yes.

like image 35
Tony Delroy Avatar answered Nov 07 '22 05:11

Tony Delroy


In addition to templatetypedef answer, it may be pointed out that you also may wrap A into

class AVirt:virtual public A{}; 

and inherit other classes from it. You wil not need to mark explicitly other inheriances as virtual in this case

like image 36
user396672 Avatar answered Nov 07 '22 05:11

user396672