Multiple inheritance occurs when a class has two or more subclassing relationships with different superclasses. In this case the "subclass" draws properties from two different superclasses:
[Amphibian] | | +--------------+----------------+ | | _ _ V V [Reptile] [Fish] +crawl() +swim()
Here an Amphibian has properties that are appropriate for both living in water, inherited from [Fish], and living on land, inherited from [Reptile].
The use of multiple inheritance is a source of considerable controversy in the OO community. While I am not dogmatic about it I tend to lean towards the camp that doesn't use it for several reasons. One reason is that it very rarely reflects the way customer problem spaces actually work. Note, for example, that in the real world of Darwinian evolution the zoological tree has Amphibians evolving from Fish and Reptiles evolving from Amphibians. Thus the notion of [Amphibian] "inheriting" properties from [Reptile] is a bit of a stretch when mapping to the problem space.
A technical problem is that multiple inheritance often violates the rule that the union of subset members of the superclass must be a complete set of the superclass' members. That's because the superclasses often don't have other subclasses that are logically siblings. In this case it is hard to imagine another subclass of, say, [Fish] that is a sibling of [Amphibian]. That should be no surprise because an Amphibian isn't a Fish in the real world. Thus the notion of "is-a" goes out the window. There is a similar technical problem in that the superclasses are necessarily joint sets because members of the subclass are also members of each superset. These technical problems open the door for a lot of foot-shooting, particularly during maintenance.
A more aesthetic reason lies in the OO emphasis on cohesion. If the superclasses are completely distinct classes, they abstract quite different entities. Those entity abstractions are presumably already cohesive in their own right. To identify a subset of those entities that has both sets of properties necessarily implies a bleeding of cohesion between the supersets as represented by the subset of entities that have both sets of properties. In other words, the subclass inheriting properties from two distinct, individually cohesive superclasses cannot be as cohesive as either superclass and that bleeds cohesion for both superclasses. This notion of cohesion drives the rule that I use to qualify when I can use multiple inheritance:
Employ multiple inheritance only when both superclasses are semantically related.
This is more easily seen by example (from "Executable UML" by Mellor and Balcer):
[Account] A | +-----------+-------------------+ | | [CheckingAccount] [SavingsAccount] A | | | +------+--------------+ | | | | [Regular] [Interest] | | | +-------+--------+ | _ V [InterestBearingAccount]
In this case [Account] and [InterestBearingAccount] are semantically related in the problem space. One can imagine a slightly different situation where [InterestBearingAccount] might even be a subclass of [Account]:
[Account] A | R1 +-----------+-------------------+ | | [CheckingAccount] [InterestBearingAccount] A A | R2 | R3 +------+-------------+ +-------------+------------+ | | | | [RegularChecking] [InterestChecking] [Savings]
Alas, though superficially quite plausible, it is illegal because of the rule that the R1 relationship's subsets must be disjoint. A member of [InterestChecking] would necessarily be a member of both [CheckingAccount] and [InterestBearingAccount], making them joint subsets. So my bottom line point here is that one employs multiple inheritance to avoid violating the two rules about disjoint, complete sets that I cited in a previous post. In that case the superclasses are semantically related and cohesion is not a problem.
I can't talk about multiple inheritance without talking about composition. Composition is a technique borrowed from functional programming via component engineering. In its original form it is used to seamlessly glue together independent functions to achieve a more complex behavior. When applied in an OO context it essentially allows one to cobble together abstractions by merging properties that already exist in other abstractions. This is a useful tool for providing very generic services so one sees it in implementations for things like layered model infrastructures that must be independent of particular problem semantics. As it happens, multiple inheritance is an ideal vehicle for providing the "glue" that cobbles disparate properties.
Unfortunately this is not a very OO approach; in fact it is rather antithetical to OO because of the emphasis on merging properties (functions) rather than abstracting problem space entities based upon their intrinsic properties. This becomes most apparent when one considers the "-able" superclasses that are all too prevalent today. These allow one to "glue" hidden infrastructure into the class to support things like streaming or display. To do so one inherits from a Streamable or Displayable class. Such classes are thinly guised function libraries and that is definitely not the OO Way. Try going into you local Honda dealer and asking to test drive a Displayable Accord.