tonyw@microsoft.UUCP (Tony Williams) (11/02/88)
In article <3438@jpl-devvax.JPL.NASA.GOV> david@beowulf.JPL.NASA.GOV (David Smyth) writes: >only use multiple-inheritance when you have the source of all classes >back to their roots because a client can introduce bugs which a C++ >compliler won't catch; etc. etc. etc. Is this described anywhere in the literature? Can anyone tell me about the problems referred to here? Tony
david@beowulf.JPL.NASA.GOV (David Smyth) (11/03/88)
tonyw@microsoft.UUCP (Tony Williams) writes: >david@beowulf.JPL.NASA.GOV (David Smyth) writes: >>only use multiple-inheritance when you have the source of all classes >>back to their roots because a client can introduce bugs which a C++ >>compliler won't catch; ... > >Is this described anywhere in the literature? Can anyone tell me about >the problems referred to here? The problem was discussed in several sessions at the USENIX C++ conference in Denver two weeks ago, including the tutorials given by Jonathan Shopiro. The concept is: any time a class exists more than once in a multiple inheritance heirarchy, then that class should be declared as "virtual." Shopiro gave the following example: class List { ... }; // Something which can be on a list. class Window { ... }; // A window. class Bordered : public List, virtual public Window { ... }; // A bordered window on a list. class Labeled : public List, virtual public Window { ... }; // A labeled window on a list. class Fancy : public Bordered, public Labeled { ... }; // A bordered, labeled window on TWO lists - // clear as mud, right? And here is more illumination: if the object library author did not specify that Window was a virtual base class of Bordered and Labeled, but just a base class, then Fancy would consist of TWO windows! You would not have one with characteristics (member data and member functions) of both, rather you would have a new class with a Bordered window, and a Labeled window. Isn't that just so obvious? Won't all of your average programmers simply have a wonderful time with this new feature? :-( Note that the object library author must consider which class may be combined with which other classes, and then declare any intersections of their inheritance heirarchies to be virtual - well, if you want that part of their characteristics to combine, any way. Simple, isn't it? :-( Note that the derived classes change radically when a base class is altered - Fancy will be on a single list, not two, if the definition of EITHER Bordered or Labeled is changed to make List a virtual base class. That is theoretically OK, since any change to a base class may impact any derived classes. Practically, however, it is a can of worms. Other problems: when Fancy overrides a Window method, its OK, because Fancy has only one Window. If it overrides a List method, nope: ambiguous. True, the compiler says so. But the author of Fancy must know the inheritance of the classes in the library to make sure the appropriate method is overriden. Notice that simply a complete list of names visible in the Bordered and Labeled classes is not enough: the client (Fancy) author must also know the internal structure of the library. Not very re-usable. And more: you lose substitutablity when you use a virtual base class. Fancy cannot be on a list of windows, because you can't cast a pointer declared as a pointer to Window into a pointer to Fancy, or Bordered or Labeled for that matter. Again, not very useful. Imagine this bug: the window system object library has a Refresh method, which causes all windows to repaint. A client creates a new class with Window a virtual base class, and overrides the refresh method (of course). Hmm. That window won't refresh. How nicely obscure. :-( What to use instead: Member objects! class List { ... }; // exactly as above. class Window { ... }; // ditto. class Border { ... }; // Border attrs and methods. class Label { ... }; // Label attrs and methods. class Bordered : Window // Inherit most significant characteristics { List borderedList; Border myBorder; ... }; // Window with border, on a list. class Labeled : Window { List labeledList; Label myLabel; ... }; // Window with label, on a list. class Fancy : Window { List labeledList, borderedList; Border myBorder; Label myLabel; ... }; // Labeled, bordered window on two lists. True, more classes, and that is always a pain. TANSTAAFL - There aint no such thing as a free lunch. There is such a thing as obscurity however. Neither member objects nor multiple inheritance provide a free lunch, but only one is obscure... Let your Joe Average programmers prove it to you!
db@lfcs.ed.ac.uk (Dave Berry) (11/11/88)
Caveat: the following article is based on my general knowledge of programming languages, my experience with C++, and David Smyth's article. I may have missed the point on several occasions. In article <3451@jpl-devvax.JPL.NASA.GOV> david@beowulf.JPL.NASA.GOV (David Smyth) writes: > >Shopiro gave the following example: > >class List { ... }; // Something which can be on a list. >class Window { ... }; // A window. > >class Bordered : > public List, virtual public Window > { ... }; // A bordered window on a list. > >class Labeled : > public List, virtual public Window > { ... }; // A labeled window on a list. > >class Fancy : > public Bordered, public Labeled > { ... }; // A bordered, labeled window on TWO lists - > // clear as mud, right? This makes sense to me. Like any language construct, the details have to be explained (think of call-by-value vs. call-by-reference vs. call-by-name procedure parameters etc.), but this construct fits with the rest of the way C++ does things. >And here is more illumination: if the object library author did not >specify that Window was a virtual base class of Bordered and Labeled, >but just a base class, then Fancy would consist of TWO windows! You >would not have one with characteristics (member data and member >functions) of both, rather you would have a new class with a Bordered >window, and a Labeled window. Usually the sharing of objects in any hierarchy has to be specified explicitly. There isn't a rule that will always give the desired results, because the desired results vary from case to case. >Note that the derived classes change radically when a base class is >altered - Fancy will be on a single list, not two, if the definition of >EITHER Bordered or Labeled is changed to make List a virtual base >class. That is theoretically OK, since any change to a base class may >impact any derived classes. Practically, however, it is a can of worms. What I find odd is the assumption that all occurrences of a virtual class will be the same. In an analogous hierarchy in ML the user can specify which sub-components are shared (i.e. the same), to any depth in the visible hierarchy. I'd expected C++ to offer the same facility. In the above example, I would have expected the programmer to have to write "sharing Bordered.List == Labeled.List" if he wanted Fancy objects to be on only one list. This might have gone some way to reducing unexpected effects from changes in base classes. Incidentally, our ML group finds that hierarchies often have a rhomdoid shape, with the "classes" at the top of the rhombus specifying lots of sharing between those in the middle. Someone using the rhombus as a whole rarely needs to know its internal hierarchy. If the same effect applies to C++, the effects may not be as bad as David expects. (To prevent misunderstanding, let me stress that the hierarchy of structures in ML is analogous to an inheritance hierachy, but is substantially different. Also, I'm comparing the languages, just one feature from each.) >Other problems: when Fancy overrides a Window method, its OK, because >Fancy has only one Window. If it overrides a List method, nope: >ambiguous. This is obvious... >True, the compiler says so. But the author of Fancy >must know the inheritance of the classes in the library to make >sure the appropriate method is overriden. Notice that simply a >complete list of names visible in the Bordered and Labeled classes >is not enough: the client (Fancy) author must also know the internal >structure of the library. Not very re-usable. So you need more documentation to use a library. How does this stop it from being re-usable? >And more: you lose substitutablity when you use a virtual base class. >Fancy cannot be on a list of windows, because you can't cast a pointer >declared as a pointer to Window into a pointer to Fancy, or Bordered >or Labeled for that matter. Now this is a problem. Can't C++ introduce a notion of cast that is more sophisticated than the C notion, to handle cases like this? >What to use instead: Member objects! > >class List { ... }; // exactly as above. >class Window { ... }; // ditto. >class Border { ... }; // Border attrs and methods. >class Label { ... }; // Label attrs and methods. > >class Bordered : Window // Inherit most significant characteristics > { List borderedList; > Border myBorder; > ... }; // Window with border, on a list. > >class Labeled : Window > { List labeledList; > Label myLabel; > ... }; // Window with label, on a list. > >class Fancy : Window > { List labeledList, borderedList; > Border myBorder; > Label myLabel; > ... }; // Labeled, bordered window on two lists. This is certainly a simpler structure. If multiple inheritance doesn't provide the possibility of lists of a given type including objects of a sub-type of that type, with virtual members working properly, I don't think multiple inheritance adds much, if anything, to the language. I guess it make function calls shorter (you don't have to qualify the member name with the class name(s)), but this hardly justifies the increased complexity. If proper lists were provided, then Jon's multiple inheritance structure would provide more facilities than David's member objects structure. The choice of which to use would depend on what facilities were needed. Having gone on at such length, I hope I've got this right... Dave Berry, Laboratory for Foundations of Computer Science, Edinburgh. db%lfcs.ed.ac.uk@nss.cs.ucl.ac.uk <Atlantic Ocean>!mcvax!ukc!lfcs!db
bs@alice.UUCP (Bjarne Stroustrup) (11/19/88)
Dave Berry, Laboratory for Foundations of Computer Science, Edinburgh. beowulf.JPL.NASA.GOV (David Smyth): > >And more: you lose substitutablity when you use a virtual base class. > >Fancy cannot be on a list of windows, because you can't cast a pointer > >declared as a pointer to Window into a pointer to Fancy, or Bordered > >or Labeled for that matter. > > Now this is a problem. Can't C++ introduce a notion of cast that is > more sophisticated than the C notion, to handle cases like this? Try virtual functions. Naturally, I could make casts work from a virtual base class to a derived class (where the derived class is unambiguously specified), but I don't see much point in such an elaboration since we already have virtual functions as a cleaner mechanism. > If multiple inheritance doesn't provide the possibility of lists of > a given type including objects of a sub-type of that type, with > virtual members working properly, I don't think multiple inheritance > adds much, if anything, to the language. Exactly, so it does.