About the memory layout
As a side note, the problem with the Dreaded Diamond is that the base class is present multiple times. So with regular inheritance, you believe you have:
A
/ \
B C
\ /
D
But in the memory layout, you have:
A A
| |
B C
\ /
D
This explain why when call A::foo(), you have an ambiguity problem. But the real problem comes when you want to use a member variable of A. For example, let's say we have:
class A
{
public :
foo() ;
int m_iValue ;
} ;
When you'll try to access m_iValue from D, the compiler will protest, because in the hierarchy, it'll see two m_iValue, not one. And if you modify one, say, B::m_iValue (that is the A::m_iValue parent of B), C::m_iValue won't be modified (that is the A::m_iValue parent of C).
This is where virtual inheritance comes handy, as with it, you'll get back to a true diamond layout, with not only one foo() method only, but also one and only one m_iValue.
What could go wrong?
Imagine:
- A has some basic feature.
- B adds to it some kind of cool array of data (for example)
- C adds to it some cool feature like an observer pattern (for example, on m_iValue).
- D inherits from B and C, and thus from A.
With normal inheritance, modifying m_iValue from D is ambiguous and this must be resolved. Even if it is, there are two m_iValues inside D, so you'd better remember that and update the two at the same time.
With virtual inheritance, modifying m_iValue from D is ok... But... Let's say that you have D. Through its B interface, you attached an observer. And through its C interface, you update the cool array, with has the side effect of directly changing m_iValue...
As the change of m_iValue is done directly (without using a virtual accessor method), the observer "listening" through B won't be called, because the code implementing the listening is in B, and C doesn't know about it...
Conclusion
If you're having a diamond in your hierarchy, it means that you have 95% to have done something wrong with said hierarchy.