I originally asked this question on StackOverflow, but I was directed here, and I think my problem is perhaps as much conceptual as technical, so here goes.
If you’re defining a hierarchy of abstract class in C++, and then creating concrete subclasses with implementations, you could end up with abstract classes something like this, say:
A / \ B1 B2
So the concrete classes then inherit like so:
B1 B2 B1 B2 | | | | C1 C2 D1 D2
And this is all fine and dandy when
Dn are just implementing the interfaces of
Bn, or where say
C2 implement the
A interface differently.
However, if I want to have some shared functionality in
C2, which comes from the
A interface, where do I put it?
It can’t go in
A, both because
A is abstract and because
Dn should not be inheriting it.
It seems like there’s a notional
A_for_C implementation, but does this belong in another ancestor class? Or in a composed sibling class?
_____A_____ _____A_____ / | \ / | \ B1 A_for_C B2 vs B1 B2 A_for_C |_____/ \____ | | | C1 C2 C1 C2 (C1 and C2 then each have an A_for_C and delegate)
The first one seems conceptually accurate, but requires
virtual inheritance, while the second requires delegation. So both impose a performance hit despite there not being any real ambiguity.
Reading around the web, on this website I find it said
Some people believe that the purpose of inheritance is code reuse. In C++, this is wrong. Stated plainly, “inheritance is not for code reuse.”
How then should the implementation be shared?
I found some relevant discussion in these questions:
- Where does this concept of "favor composition over inheritance" come from?
- Interface and Inheritance: Best of both worlds?
- Is there any "real" reason multiple inheritance is hated?
I think utnapistim’s answer below is much pithier and to the point than these, and helped me mentally cut through a lot of what these other questions/answers discuss.
Inheritance is about agreeing to fulfil a contract. Multiple inheritance is fine if the subclass really does guarantee fulfilling the parent contracts.
Implementation, however, is only really the concern of the final object. Yes, it might be convenient to inherit the implementation sometimes, but that’s actually orthogonal to the interface, and there are various techniques for pulling in an implementation other than the default v-table-based approach, including:
(Which are, I think, equivalent except that one is compile-time and one is run-time.)