How Does Virtual Inheritance Solve the "Diamond" (Multiple Inheritance) Ambiguity

How does virtual inheritance solve the diamond (multiple inheritance) ambiguity?

You want: (Achievable with virtual inheritance)

  A  
/ \
B C
\ /
D

And not: (What happens without virtual inheritance)

A   A  
| |
B C
\ /
D

Virtual inheritance means that there will be only 1 instance of the base A class not 2.

Your type D would have 2 vtable pointers (you can see them in the first diagram), one for B and one for C who virtually inherit A. D's object size is increased because it stores 2 pointers now; however there is only one A now.

So B::A and C::A are the same and so there can be no ambiguous calls from D. If you don't use virtual inheritance you have the second diagram above. And any call to a member of A then becomes ambiguous and you need to specify which path you want to take.

Wikipedia has another good rundown and example here

Diamond problem with Multiple inheritance C++

 InService(string _name, int _sex, string _sno, string _wno){
Person::name = _name;
Person::sex - _sex;
Worker::wno = _wno;
Student::sno = _sno;
}

There's a typo there, Person::sex - _sex; should be Person::sex = _sex;

Also you can remove name and sex virtual function and have it just a standard function in Person, since it's exactly the same for all classes that derive from it. That will remove the ambiguity of which getName and getSex function that InService class virtual table needs to point to.

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

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.

try except and inheritance

Your classes are some variant of the deadly diamond of death case of multiple inheritance: the base class A is twice a base class of C: once directly, and once indirectly via B:

              A
|\
| \
| B
| /
|/
C

The consequence of this kind of inheritance graph is that your C object has two different A sub-objects. I name them in the graph for convenience:

            a1:A a2:A
| |
| |
| b:B
| /
| /
c:C

As you see, if you mention the A sub-object, there is an ambiguity: is it a1 or a2, whereas for B there is no ambiguity. And this impacts the matching the exception handling. Because the standard's rules are as follows:

[except.handle]/3: A handler is a match for an exception object of type E if

— The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or

— the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or

— ...

When you throw C, first the catch (A a) is tested: A is not the same type than C. And A is non an unambiguous public base class either. Then the next catch (B b) is tested: B is not the same type than C. But it's an unambiguous public base class of it. Match! And therefore your result.

Solution 1:

Making A a virtual base of both B and C as suggested in the other answer, ensures that there is only one unique and unambiguous A sub-object in C. This is why it works.

Solution 2

Virtual inheritance is not a silver bullet. As soon as you have non-default constructors, you must explicit the virtual constructor systematically. Moreover, you must use the virtual inheritance wherever A is a subclass. Last but not least, virtual inheritance is sometimes just not suitable for the needs (for example if two independent strands are needed).

In this last situation, you could also disambiguate by introducing an intermediary class:

class AA : public A {}; 
class C : public AA, public B {};

and then catch (AA& a) instead of catching A. This is stupid simple, but it removes the ambiguity, whereas there are still two different A sub-objects (again: only if you need them). (online demo)

Recommendation: To get a full understanding of the situation, I recommend that you try to update your example and add in each class some members that you'd initialize with a non-default constructor. ANd yes, catch the exception by reference.



Related Topics



Leave a reply



Submit