This section covers a wide spectrum of questions/answers, ranging from the high-level / strategy / design issues, going all the way down to low-level / tactical / programming issues. We cover them in that order.
Please make sure you understand the high-level / strategy / design issues. Too many programmers worry about getting "it" to compile without first deciding whether they really want "it" in the first place. So please read the first several FAQs in this section before worrying about the (important) mechanical details in the last several FAQs.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Grrrrrrrrr.
It really bothers me when people think they know what's best for your problem even though they've never seen your problem!! How can anybody possibly know that multiple inheritance won't help you accomplish your goals without knowing your goals?!?!?!?!!!
Next time somebody tells you that you should never use multiple inheritance, look them straight in the eye and say, "One size does not fit all." If they respond with something about their bad experience on their project, look them in the eye and repeat, slower this time, "One size does not fit all."
People who spout off one-size-fits-all rules presume to make your design decisions without knowing your requirements. They don't know where you're going but know how you should get there.
Don't trust an answer from someone who doesn't know the question.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Of course there are!
You won't use it all the time. You might not even use it regularly. But there are some situations where a solution with multiple inheritance is cheaper to build, debug, test, optimize, and maintain than a solution without multiple inheritance. If multiple inheritance cuts your costs, improves your schedule, reduces your risk, and performs well, then please use it.
On the other hand, just because it's there doesn't mean you should use it. Like any tool, use the right tool for the job. If MI (multiple inheritance) helps, use it; if not, don't. And if you have a bad experience with it, don't blame the tool. Take responsibility for your mistakes, and say, "I used the wrong tool for the job; it was my fault." Do not say, "Since it didn't help my problem, it's bad for all problems in all industries across all time." Good workmen never blame their tools.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
M.I. rule of thumb #1: Use inheritance only if doing so will remove if / switch statements from the caller code. Rationale: this steers people away from "gratuitous" inheritance (either of the single or multiple variety), which is often a good thing. There are a few times when you'll use inheritance without dynamic binding, but beware: if you do that a lot, you may have been infected with wrong thinking. In particular, inheritance is not for code-reuse. You sometimes get a little code reuse via inheritance, but the primary purpose for inheritance is dynamic binding, and that is for flexibility. Composition is for code reuse, inheritance is for flexibility. This rule of thumb isn't specific to MI, but is generic to all usages of inheritance.
M.I. rule of thumb #2: Try especially hard to use ABCs when you use MI. In particular, most classes above the join class (and often the join class itself) should be ABCs. In this context, "ABC" doesn't simply mean "a class with at least one pure virtual function"; it actually means a pure ABC, meaning a class with as little data as possible (often none), and with most (often all) its methods being pure virtual. Rationale: this discipline helps you avoid situations where you need to inherit data or code along two paths, plus it encourages you to use inheritance properly. This second goal is subtle but is extremely powerful. In particular, if you're in the habit of using inheritance for code reuse (dubious at best; see above), this rule of thumb will steer you away from MI and perhaps (hopefully!) away from inheritance-for-code-reuse in the first place. In other words, this rule of thumb tends to push people toward inheritance-for-interface-substitutability, which is always safe, and away from inheritance-just-to-help-me-write-less-code-in-my-derived-class, which is often (not always) unsafe.
M.I. rule of thumb #3: Consider the "bridge" pattern or nested generalization as possible alternatives to multiple inheritance. This does not imply that there is something "wrong" with MI; it simply implies that there are at least three alternatives, and a wise designer checks out all the alternatives before choosing which is best.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Suppose you have land vehicles, water vehicles, air vehicles, and space vehicles. (Forget the whole concept of amphibious vehicles for this example; pretend they don't exist for this illustration.) Suppose we also have different power sources: gas powered, wind powered, nuclear powered, pedal powered, etc. We could use multiple inheritance to tie everything together, but before we do, we should ask a few tough questions:
If both answers are "yes," multiple inheritance is probably the best way to go. But before you close the door on the alternatives, here are a few more "decision criteria." Suppose there are N geographies (land, water, air, space, etc.) and M power sources (gas, nuclear, wind, pedal, etc.). There are at least three choices for the overall design: the bridge pattern, nested generalization, and multiple inheritance. Each has its pros/cons:
The most important point: there is no universally "best" answer. Perhaps you were hoping I would tell you to always use one or the other of the above choices. I'd be happy to do that except for one minor detail: it'd be a lie. If exactly one of the above was always best, then one size would fit all, and we know it does not.
So here's what you have to do: T H I N K. You'll have to make a decision. I'll give you some guidelines, but ultimately you will have to decide what is best (or perhaps "least bad") for your situation.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
The following matrix gives an overview of the pros/cons:
Does it grow gracefully when adding geography or power source? | |||
How much code needs to be written? | (N+M chunks) |
(N*M chunks) |
(N*M chunks) |
Do you have fine granular control over the algorithms and data structures? | |||
Do you have fine granular control over nonsensical combinations? | |||
Does it let users treat either base class polymorphically? | |||
Does it let derived classes share common code from either side? |
Warning: the reader should not be naive in using the above matrix. For
example, do not simply add up the number of and
marks, then decide based on which design has the most
good and least bad. The first step in using the above matrix is to find out
if there are additional design approaches, that is, additional columns. And
don't forget: the bridge and nested generalization columns are really both
pairs of columns, since in both cases there is an asymmetry that could
go in either direction. In other words, one could put an
The second step in using the above matrix is to give a "weight" to each row. For example, in your particular situation, the amount of code that must get written (second row) may be more or less important than the granular control over data structures. The ultimate decision will be made by finding out which approach is best for your situation. One size does not fit all do not expect the answer in one project to be the same as the answer in another project.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
This second example is only slightly different from the previous since it is more obviously symmetric. This symmetry tilts the scales slightly toward the multiple inheritance solution, but one of the others still might be best in some situations.
In this example, we have only two categories of vehicles: land vehicles and water vehicles. Then somebody points out that we need amphibious vehicles. Now we get to the good part: the questions.
If we get three "yes" answers, multiple inheritance is probably the right choice. To be sure, you should ask the other questions as well, e.g., the grow-gracefully issue, the granularity of control issues, etc.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
The "dreaded diamond" refers to a class structure in which a particular class appears more than once in a class's inheritance hierarchy. For example,
Forgive the ASCII-art, but the inheritance hierarchy looks something like this:
Before we explain why the dreaded diamond is dreaded, it is important to note that C++ provides techniques to deal with each of the "dreads." In other words, this structure is often called the dreaded diamond, but it really isn't dreaded; it's more just something to be aware of.
The key is to realize that Base is inherited twice, which means any
data members declared in Base, such as data_ above, will
appear twice within a Join object. This can create ambiguities: which
data_ did you want to change? For the same reason the conversion from
C++ lets you resolve the ambiguities. For example, instead of saying
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Just below the top of the diamond, not at the join-class.
To avoid the duplicated base class subobject that occurs with the "dreaded diamond", you should use the virtual keyword in the inheritance part of the classes that derive directly from the top of the diamond:
Because of the virtual keyword in the base-class portion of Der1 and Der2, an instance of Join will have have only a single Base subobject. This eliminates the ambiguities. This is usually better than using full qualification as described in the previous FAQ.
For emphasis, the virtual keyword goes in the hierarchy above Der1 and Der2. It doesn't help to put the virtual keyword in the Join class itself. In other words, you have to know that a join class will exist when you are creating class Der1 and Der2.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Consider the following example:
Believe it or not, when
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Generally, virtual base classes are most suitable when the classes that derive from the virtual base, and especially the virtual base itself, are pure abstract classes. This means the classes above the "join class" have very little if any data.
Note: even if the virtual base itself is a pure abstract class with no member data, you still probably don't want to remove the virtual inheritance within classes Der1 and Der2. You can use fully qualified names to resolve any ambiguities that arise, and you might even be able to squeeze out a few cycles in some cases, however the object's address is somewhat ambiguous (there are still two Base class subobjects in the Join object), so simple things like trying to find out if two pointers point at the same instance might be tricky. Just be careful very careful.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Initialization list of most-derived-class's ctor directly invokes the virtual base class's ctor.
Because a virtual base class subobject occurs only once in an instance, there are special rules to make sure the virtual base class's constructor and destructor get called exactly once per instance. The C++ rules say that virtual base classes are constructed before all non-virtual base classes. The thing you as a programmer need to know is this: constructors for virtual base classes anywhere in your class's inheritance hierarchy are called by the "most derived" class's constructor.
Practically speaking, this means that when you create a concrete class that has a virtual base class, you must be prepared to pass whatever parameters are required to call the virtual base class's constructor. And, of course, if there are several virtual base classes anywhere in your classes ancestry, you must be prepared to call all their constructors. This might mean that the most-derived class's constructor needs more parameters than you might otherwise think.
However, if the author of the virtual base class followed the guideline in the previous FAQ, then the virtual base class's constructor probably takes no parameters since it doesn't have any data to initialize. This means (fortunately!) the authors of the concrete classes that inherit eventually from the virtual base class do not need to worry about taking extra parameters to pass to the virtual base class's ctor.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
No C-style downcasts; use dynamic_cast instead.
(Rest to be written.)
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
The very first constructors to be executed are the virtual base classes anywhere in the hierarchy. They are executed in the order they appear in a depth-first left-to-right traversal of the graph of base classes, where left to right refer to the order of appearance of base class names.
After all virtual base class constructors are finished, the construction order is generally from base class to derived class. The details are easiest to understand if you imagine that the very first thing the compiler does in the derived class's ctor is to make a hidden call to the ctors of its non-virtual base classes (hint: that's the way many compilers actually do it). So if class D inherits multiply from B1 and B2, the constructor for B1 executes first, then the constructor for B2, then the constructor for D. This rule is applied recursively; for example, if B1 inherits from B1a and B1b, and B2 inherits from B2a and B2b, then the final order is B1a, B1b, B1, B2a, B2b, B2, D.
Note that the order B1 and then B2 (or B1a then B1b) is determined by the order that the base classes appear in the declaration of the class, not in the order that the initializer appears in the derived class's initialization list.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
Short answer: the exact opposite of the constructor order.
Long answer: suppose the "most derived" class is D, meaning the actual object that was originally created was of class D, and that D inherits multiply (and non-virtually) from B1 and B2. The sub-object corresponding to most-derived class D runs first, followed by the dtors for its non-virtual base classes in reverse declaration-order. Thus the destructor order will be D, B2, B1. This rule is applied recursively; for example, if B1 inherits from B1a and B1b, and B2 inherits from B2a and B2b, the final order is D, B2, B2b, B2a, B1, B1b, B1a.
After all this is finished, virtual base classes that appear anywhere in the hierarchy are handled. The destructors for these virtual base classes are executed in the reverse order they appear in a depth-first left-to-right traversal of the graph of base classes, where left to right refer to the order of appearance of base class names. For instance, if the virtual base classes in that traversal order are V1, V1, V1, V2, V1, V2, V2, V1, V3, V1, V2, the unique ones are V1, V2, V3, and the final-final order is D, B2, B2b, B2a, B1, B1b, B1a, V3, V2, V1.
Reminder to make your base class's destructor virtual, at least in the normal case. If you don't thoroughly understand the rules for why you make your base class's destructor virtual, then either learn the rationale or just trust me and make them virtual.
[ Top | Bottom | Previous section | Next section | Search the FAQ ]
E-mail the author
[ C++ FAQ Lite
| Table of contents
| Subject index
| About the author
| ©
| Download your own copy ]
Revised Mar 1, 2006