Although this is a messy example, it does occur in practice, and it’s a good demonstration of where multiple inheritance is clearly necessary: You must be able to upcast to both base classes.
Summary
One reason MI exists in C++ is that it is a hybrid language and couldn’t enforce a single monolithic class hierarchy the way Smalltalk and Java do. 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 still be resolved). If a diamond appears, you may want to eliminate 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 ’90s."[115] 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 might want to take a look at your reasoning. A good Occam’s Razor is to ask, "Must I upcast to all the base classes?" If not, your life will be easier if you embed instances of all the classes you
Exercises
These exercises will take you step by step through the complexities of MI.
1. Create a base class X with a single constructor that takes an int argument and a member function f( ), which takes no arguments and returns void. Now derive Y and Z from X, creating constructors for each of them that take a single int argument. Now derive A from Y and Z. Create an object of class A, and call f( ) for that object. Fix the problem with explicit disambiguation.
60. Starting with the results of exercise 1, create a pointer to an X called px, and assign to it the address of the object of type A you created before. Fix the problem using a virtual base class. Now fix X so you no longer have to call the constructor for X inside A.
61. Starting with the results of exercise 2, remove the explicit disambiguation for f( ), and see if you can call f( ) through px. Trace it to see which function gets called. Fix the problem so the correct function will be called in a class hierarchy.
10: Design patterns
"… describe a problem which occurs over and over again in our environment, and then describe the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice" —Christopher Alexander
This chapter introduces the important and yet nontraditional "patterns" approach to program design.
In recent times, the most important step forward in object-oriented design is probably the "design patterns" movement, chronicled in
The pattern concept
Initially, you can think of a pattern as an especially clever and insightful way to solve a particular class of problems. That is, it looks like a lot of people have worked out all the angles of a problem and have come up with the most general, flexible solution for that type of problem. The problem could be one you have seen and solved before, but your solution probably didn’t have the kind of completeness you’ll see embodied in a pattern. Furthermore, the pattern exists independently of any particular implementation and, indeed, can be implemented in a number of ways.
Although they’re called "design patterns," they really aren’t tied to the realm of design. A pattern seems to stand apart from the traditional way of thinking about analysis, design, and implementation. Instead, a pattern embodies a complete idea within a program, and thus it can sometimes also span the analysis phase and high-level design phase. Because a pattern has a direct implementation in code, you might not expect it to show up before low-level design or implementation (and in fact you might not realize that you need a particular pattern until you get to those phases).