TITLE: Inheritance, libraries, and mixins This picks up on a dialog between srheintze@happy.uccs.edu (SRH) and John Skaller (JMS) on May 15, 1993 ... SRH: I would like to recap some of the discussions here and in the periodicals about inheritance. (1) The biggest problem seems to be the name inheritance. Programmers get confused by the term inheritance when they try to use the term like a biologist might. You may say you inherited your blue eyes from you mother. What does this mean? This means you and all other humans have an attribute called eye color and you received the value of blue from your mother. This works for biologists, but not for programmers. Inheritance in programming is only effective if we inherit attributes instead of values for attributes. JMS: Inheritance only works for abstract functionality. Basically: only useful for abstract classes, and this sort of inheritance is *composition*. The sort of derivation called *subclassing* is for *implementing* abstractions: there is no inheritance here at all, on the contrary, instead of inheriting some attribute you are *defining* it. SRH: (2) Another problem is using the inheritance feature to model *dynamic classification*. We have dynamic classification if we say a sea plane inherits from boat and plane. The inheritance feature in C++ is inappropriate here because a vehical is either a boat or a plane, not both at the same time. A sea plane is better modeled as a class that contains a pointer to a state diagram that implements a finite state automoton. JMS: [...] (3) There is an important distinction between natural and synthetic inheritance. Synthetic inheritance is just using the feature in the language because it makes the programmer's job easier which is in contrast to natural inheritance which is using the feature in the language to express a relationship that has been observed (i.e., abstracted) from the application domain. Using MI for natural inheritance seems to be very rare in the literature. JMS: [...] SRH: Do you think mix-ins are mainly used for natural or synthetic inheritance? The examples of mix-ins that John Skaller and I have come up with make wonder if mix-ins are primarly used for synthetic inheritance. JMS: Its confused because the Smalltalk idea of inheritance and subclassing is unrelated to the C++ idea. In C++, subclassing is for *implementing* an abstraction. It embodies the 'isA' relation. In C++ one 'type' cannot *be* another type. You can only have an *implementation* of a type, or, another implementation of the *same* type. The implementations should be hidden .. so the user only sees one type: the subclasses are invisible. Is this natural or synthetic? I dont know, but I think you are probably correct in saying its synthetic. What that means is that inheritance is *never* used for natural purposes in C++ because every such use is an error. (Or rather, when it *is* used, it is used with abstractions where we really mean to use composition not subclassing) But most of the OO examples in the literature are 'natural' ones that Smalltalk supports. C++ doesnt support that paradigm at all. (well, it does if you use downcasting and RTTI :-) SRH: If you don't count the mix-in examples as natural multiple inheritance, then the only example of natural multiple inheritance I have seen, after repeatedly soliciting this news group for examples, is the bank account example. (First I solicited for text book examples of MI, and then any example of MI). JMS: Gak. Thats not a good example really. SRH: (4) Composition is sometimes superior to inheritance because you can give the sub-object a name thus making your code much easier to deal with. JMS: Its not 'sometimes superior' as if you ought to have a choice. Either you should use composition or subclassing. You may have to decide what you really mean: that design choice determines the correct technique usually. SRH: If you want your class to receive additional member functions, there is merit to making your member object public. This is much easier than manually alleviating a lot of conflicting member function names resulting from multiple inheritance. JMS: I've never had a problem with conflicting member names using MI. SRH: (5) Virtual inheritance is really messy as pointed out in the recent article in the C++ report on FJI (Fork Join Inheritance). The fact that initializer lists are ingored for all but the most derived class is very frightening because it is a terribly violation of the user-provider model of programming where the user is permitted to be ignorant of the implementation of the classes he uses. JMS: Not really. Generally, virtual bases are abstract classes, and dont need *any* initialisation (other than vtable set up). How do you initialise an abstraction? SRH: Incidently, FJI is very interesting. Has anybody come up with a use for it? It is especially interesting when the same sub-object can have both virtual and non-virtual paths to the same descendant. This really boggles my mind -- why would anybody even want to think about doing that? Looks to me like FJI could cause permanent drain bamage (typos intended) in a maintenance programmer. JMS: No, it can be confusing and hard to figure out .. it sure is hard to debug, but it is essential to come to grips with it because it is *the* technique of polymorphism. Anything less than mixins is relatively powerless, anything more is not safe. SRH: (6) Even if you are astute enough to effectively grasp points 1-4, we still have the problem that most compiler writers don't understand MI. At this time it is not reasonable to assume that your compiler implements MI correctly. See Tom Cargill's book for a short test program that reveals a bug in most compilers. JMS: Right. According to my latest survey a few months ago, about 1/3 compilers got it right .. but the number is rapidly increasing. Dont forget there is no standard for the compiler writers to meet: its hard to produce a complex program without a proper specification. SRH: (7) Mix-ins are difficult to use in C++ as Bjarne explaines in the ARM (page 202). This is because unlike LISP, C++ does not have deamons (which is a feature that allows method combination). Neither does C++ have a feature like Smalltalk's super-send mechanism to preform preprocessing. I'm working on a tutorial on how to simulate deamons in C++ to make mix-ins work -- but I have not seen anyone else write about it. Does anyone know what other OO languages do about this problem of method mixing? What does simula and eiffel do about it for example? JMS: This is largely unrelated to what I call mixins. Dont confuse Multiple inheritance with mixins: MI is one of the language features required to implement mixins. For me, mixin technology is about incremental development. It is enabled by use of polymorphism: late binding. This has very little to do with delegating to a base class function. It has nothing to do with method mixing. It has to do with sideways definition of pure virtual functions. SRH: Well does that sum it up? Have I missed anything? JMS: Mm. You did cover quite a lot. I more or less claim mixins are the proper way to use inheritance for subclassing, that is, how to get the maximum benefit out of polymorphism and also achieve reusability. I dont know *any* other technique that is statically secure that does this. Delegation is more powerful than mixins, and function pointers are more powerful than delegation, but neither is completely safe. AbstractClass1 AbstractClass2 / \ / \ Implement1 AbstractClass12 Implement2 \ | / ConcreteMixinClassNumber12 You can switch Implement1 for Implement1A and the same for Implement2 *independently*. Thats the key point: you can construct the abstraction "AbstractClass12" and the mixin class as you need them, and choose the particular implementation subclasses as you need. The idea of a mixin library is to have mainly the AbstractClassX classes and the ImplementX classes. The rest is done by the programmer as required. So the library consists of components like: AbstractClass1 // abstract base / | \ Impl1 Impl2 Impl3 // implementation subclasses Can you see how this is nice? Its called mixins because you can mix-up the abstractions (design) you want, then make them from selected parts (code) that you think are suitable/optimal. The Implementation classes are 'plug compatible'. Mixing up the abstractions is like designing the circuit boards. So you have both design reuse and code reuse of library components. The major problem with this technique is thats its *really hard* to design the abstractions so they work together well. You have to do this to prevent your library exploding with abstractions. And to prevent pwogwammer bwane dambage (sic :-).