Take the following hierarchy:
- base class
A
B
, C
and E
inherits from A
D
inherits both from B
and C
F
inherits from D
and E
Talking that in code:
class A { public: int a; }
class B : public A { }
class C : public A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }
or, a diagram:
A A A
| | |
| | |
B C E
\ / /
\ / /
D /
\ /
F
With this structure, every B, C and E holds their own copy of A. Following on that, D holds a copy of B and C, F holds a copy of D and E.
This causes a problem:
D d;
d.a = 10; // ERROR! B::a or C::a?
For such cases, you use virtual inheritance, creating a "diamond":
A A
/ \ |
/ \ |
B C E
\ / /
\ / /
D /
\ /
F
or, in code:
class A { public: int a; }
class B : public virtual A { }
class C : public virtual A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }
Now you solve the previous problem, since B::a
and C::a
shares the same memory, but the same problem is still present, in another level:
F f;
f.a = 10; // ERROR: D::a or E::a ?
This part I am not sureConfirmed: you could use virtual
inheritance from A for E to solve the problem here too. But I will leave it as it is, in order to answer another point: mixing virtual and non-virtual inheritance.
But consider that you want to E::a
from a F
have a different value of D::a
from the same F
. For that, you must type-cast your F
:
F *f = new F;
(static_cast<D*>(f))->a = 10;
(static_cast<E*>(f))->a = 20;
Now your F* f
holds two different values of A::a
.
About memory management
Taking these classes, from above example:
class A { public: int a; }
class B : public virtual A { }
class C : public virtual A { }
class D : public B, public C { }
class E : public A { }
class F : public D, public E { }
One could draw the following memory diagram:
For class A:
+---------+
| A |
+---------+
For classes B, C and E:
+---------+
| B |
+---------+
|
V
+---------+
| A |
+---------+
Meaning that for every instance of B, C and E you create, you create another instance of A.
For class D, things are a bit more complicated:
+---------------------------------------+
| D |
+---------------------------------------+
| |
V V
+--------------+ +--------------+
| B | | C |
+--------------+ +--------------|
| |
V V
+---------------------------------------+
| A |
+---------------------------------------+
Meaning that when you create an D, you have one instance of B and one instance of C. But instead of creating a new instance of A for each B and C, a single instance is created for both.
And for F:
+-------------------------------------------------------+
| F |
+-------------------------------------------------------+
| |
V V
+---------------------------------------+ +---------+
| D | | E |
+---------------------------------------+ +---------+
| | |
V V |
+--------------+ +--------------+ |
| B | | C | |
+--------------+ +--------------+ |
| | |
V V V
+---------------------------------------+ +---------+
| A | | A |
+---------------------------------------+ +---------+
Meaning that when you create a F, you have: one instance of D and one of E. Since E does not virtually inherits from A, a new instance of A is created when E is created.
Concerning virtual and pure virtual methods
Take these classes:
class A { virtual void f() = 0; }
class B : public A { virtual void f(int value) { std::cout << "bar" << value; } }
class C : public B { virtual void f() { std::cout << "foo"; f(42); } }
A
is called abstract
(some also call interface
), since there are pure virtual functions.
B
is also abstract
, since it inherits from A
and does not override the A::f(void)
method, which is pure virtual, even defining its own methods (B::f(int)
)
C
is an implementation
of B
, since it does define all functions that are required to turn B
into a "full" class - which is overriding A::f(void)
.
This answer is not complete, but it gives a general idea.