Consider this C++ code:
#include <iostream>
using namespace std;
struct B {
virtual int f() { return 1; }
int g() { return 2; }
};
struct D1 : public B { // (*)
int g() { return 3; }
};
struct D2 : public B { // (*)
virtual int f() { return 4; }
};
struct M : public D1, public D2 {
int g() { return 5; }
};
int main() {
M m;
D1* d1 = &m;
cout << d1->f()
<< static_cast<D2&>(m).g()
<< static_cast<B*>(d1)->g()
<< m.g();
}
It prints 1225
. If we make virtual inheritance, i.e. add virtual
before public
in lines marked with (*), it prints 4225
.
1
changes to 4
?static_cast<D2&>(m)
and static_cast<B*>(d1)
?Polymorphism can be carried out through inheritance, with subclasses making use of base class methods or overriding them.
Inheritance is one in which a new class is created (derived class) that inherits the features from the already existing class(Base class). Whereas polymorphism is that which can be defined in multiple forms.
Real polymorphism in General can not be acheived without inheritance. Languages that are not object-oriented provide forms of polymorphism which do not rely on inheritance (i.e parametric polymorphism). Some of this is possible in Java through generics.
Inheritance is a property pertaining to just classes whereas, polymorphism extends itself into any method and/or function. Inheritance allows the derived class to use all the functions and variables declared in the base class without explicitly defining them again.
Pictures speak louder than words, so before the answers...
Class M hierarchy WITHOUT virtual base inheritance of B for D1 and D2:
M
/ \
D1 D2
| |
B B
Class M hierarchy WITH virtual base inheritance of B for D1 and D2:
M
/ \
D1 D2
\ /
B
Cross-Delegation, or as I like to call it, sibling-polymorphism with a twist. The virtual base inheritance will fix up the B::f() override to be D2:f(). Hopefully the picture helps explain this when you consider where the virtual functions are implemented, and what they override as a result of the inheritance chains.
the static_cast
operator usage in this case drives conversion from derived-to-base class types.
Lots of experience reading really bad code and knowing how the underpinnings of the language 'work'
Thankfully no. It is not common. The original iostream libraries would have given you nightmares, though, if this is at-all confusing.
Can you explain why 1 changes to 4?
Why does it change to 4
? Because of cross-delegation.
Here's the inheritance graph before virtual inheritance:
B B
| |
D1 D2
\ /
M
d1
is a D1
, so it has no idea that D2
even exists, and its parent (B
) has no idea that D2
exists. The only possible result is that B::f()
is called.
After virtual inheritance is added, the base classes are merged together.
B
/ \
D1 D2
\ /
M
Here, when you ask d1
for f()
, it looks to its parent. Now, they share the same B
, so B
's f()
will be overridden by D2::f()
and you get 4
.
Yes, this is weird because it means that D1
has managed to call a function from D2
, which is knows nothing about. This is one of the more odd parts of C++ and it is generally avoided.
Can you explain meaning of static_cast(m) and static_cast(d1)?
What don't you understand? They cast m
and d1
to D2&
and B*
respectively.
How you are you not getting lost in this kind of combinations? Are you drawing something?
Not in this case. It's complicated, but small enough to keep in your head. I've drawn the graph in the above example to make things as clear as possible.
Is it common to spot such complex settings in normal projects?
No. Everyone knows to avoid the dreaded diamond pattern of inheritance because it's simply too complicated, and there's usually a simpler way to do whatever you want to do.
In general, it's better to prefer composition over inheritance.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With