Multiple inheritance seems like such a powerful and useful tool for many situations. I understand the problems associated with the dreaded diamond and an easy solution is to always inherit with virtual base classes.
The reason MI exists in C++ and not in other OOP languages is that C++ is a hybrid language and couldn’t enforce a single monolithic class hierarchy the way Smalltalk does. Instead, C++ allows many inheritance trees to be formed, so sometimes you may need to combine the interfaces from two or more trees into a new class. If no “diamonds” appear in your class hierarchy, MI is fairly simple (although identical function signatures in base classes must be resolved). If a diamond appears, then you must deal with the problems of duplicate subobjects by introducing virtual base classes. This not only adds confusion, but the underlying representation becomes more complex and less efficient.
Multiple inheritance has been called the “goto of the 90’s”.22 This seems appropriate because, like a goto, MI is best avoided in normal programming, but can occasionally be very useful. It’s a “minor” but more advanced feature of C++, designed to solve problems that arise in special situations. If you find yourself using it often, you may want to take a look at your reasoning. A good Occam’s Razor is to ask, “Must I upcast to all of the base classes?” If not,
your life will be easier if you embed instances of all the classes you don’t need to upcast to.
This quote tells me that MI is not appropriate in most situations where I would think to use it. In what situations is it designed for?
In the examples below, are there better techniques?
1 2 3
class Herbivore : Animal { void gather(); };
class Carnivore : Animal { void hunt(); };
class Omnivore : virtualpublic Herbivore, virtualpublic Carnivore {};
or
1 2 3 4 5
class Janitor { void Clean(); };
class Father { void Play(); };
class Mother { void Nurse(); };
class Steve : virtualpublic Janitor, virtualpublic Father {} steve;
class Cindy : virtualpublic Janitor, virtualpublic Mother {} cindy;
Edit: I was just thinking about the second case. Steve "IS A" janitor and "IS A" father. That's why I inherited from both. But perhaps I should have said: Steve "HAS A" job. A Janitor "IS A" Job. Steve "IS A" father. That would look like:
1 2 3 4 5 6 7 8 9
class Janitor : Job { void Clean(); };
class Father : { void Play(); };
class Steve : public Father
{
Job job;
public:
Steve() : job( Janitor() ) {}
} steve;
I guess the main difference is that I have to create methods manually to interface with Job now.
Usually you should only use MI when dealing with interface classes (aka pure virtual classes). Sometimes MI can be used with bormal classes, but you should try hard to avoid "diamond inheritance" — it will lead to all sort of problems. But even this shouldn't be the absolute rule: i.e. in standart library iostream class inherits from istream and ostream, each of whom inherits from stream.
Typically you should only use MI when dealing with User Interface classes.
Both of your examples are bad because lets say you add a method void eat(); to the parents. How do you decide which way it gets implemented in the children etc?
MI is usually the sign of a bad design that should be rethought.
> Steve "IS A" father.
Steve `behaves' as a father to Johnny, but as a son to Mary.
A DNA test (followed by divorce) dismiss him from the first relationship.
Devastated, he makes a trip around the world.
Sadly, gets a parasite that produces hormonal changes, causing rejuvenation and sex change.
Now she's called Jane and in the third month of pregnancy. ¡Congratulations!
Thanks guys. I'm still trying to wrap my head around OOP.
ne555, you made my morning.
Edit: I've realized that you've actually destroyed my thoughts that OOP makes things easier. If I seriously wanted to support all of those statements, I'd be in a tangled mess.