sdm@cs.brown.edu (Scott Meyers) (02/13/91)
Regarding MI vs member containment, I wrote: | Private inheritance is a convenient way to share code when there is no | other conceptual relationship between two classes. I usually call this | "code stealing." When stealing the code in another class, you often have | to do some class specific stuff; this is where virtual functions come in. | The member functions in the (private) base class can call virtual functions | to do the customization. This won't work with member objects, because they | have no way of knowing what they're a part of. Mark Linton responded: | The case you refer to is pretty pathological. Since Derived privately | inherits from Base, the only place you can call a Base method from a | Derived object is in Derived itself. Then Base::method calls a virtual | on Base that Derived redefines and you end back in a Derived method. The complete design involves three classes, not just two, as I explain below. With all the pieces put together, you can call Base methods through a more general pointer (to a different base class). | class Base { virtual void f(); void g(); }; | class Derived : private Base { virtual void f(); void h(); }; | | void Base::f() { } | void Base::g() { shared_before_stuff(); f(); shared_after_stuff(); } | void Derived::f() { ... } | void Derived::h() { g(); } | | Note that Derived::f() can't be called directly (except from another | Derived method, of course). | | You refer to private inheritance as "convenient", but I have never needed | the circular control flow above. We don't use it at all in InterViews | (about 100K lines of C++). Most examples of code sharing I have seen are | better implemented with either separate types or templates (or the macro | equivalent). If you have a real example of "convenient" private | inheritance, I'd be interested in seeing it. The flow of control is convoluted, no doubt about it, but I don't find it any more convoluted than any other flow of control using virtual functions with derived objects. My use is "real" in that it's part of my research prototype (a measly 7K lines of C++) for representing software, and I find it better than the alternative designs. I'd be happy to send the code to Mark (or anyone else) who wants to see the nitty gritty details of what I do, but the general idea is as follows. My system works with views of programs. The protocol for a view is defined in the class View, which looks more or less like this: class View { protected: View(); virtual ~View(); public: virtual function1() {} virtual function2() {} ... }; The public functions declare all the things that can be done to a view, and their bodies are empty because the default action for any of these functions is to do nothing; this lets specific views only implement as much of the protocol as they like. Two particular views are View1 and View2: class View1: public View { public: virtual function1(); virtual function2(); ... }; class View2: public View { public: virtual function1(); virtual function2(); ... }; Implementation of View1 was straightforward, but during implementation of View2, it was noted that a *lot* of the code was the same for View2 as it had been for View1, except for some minor customizations. This wasn't surprising; View1 and View2 use similar algorithms and data structures, but display the results of their analysis somewhat differently. So instead of duplicating code, the design was modified so that View2 inherited privately from View1 and used private virtual functions to provide the necessary customizations: class View1: virtual public View { public: virtual function1(); virtual function2(); ... private: virtual customization1(); virtual customization2(); ... }; class View2: virtual public View, private View1 { private: virtual customization1(); virtual customization2(); ... }; View2 no longer defines most of the functions it inherits from View1, specifying instead only the necessary customizations. There is no ambiguity regarding which version of function1(), function2(), etc., to use with a View2 object because the versions in View1 dominate those in View; see ARM p. 205. That's the basic design. I find it conceptually clean, and it works well for me. However, for reasons I won't get into, although I have View* pointers floating around and I invoke virtuals through them, I never directly convert a View2* into a View*. That is, I never do something like this: View* vp = new View2; As far as I can tell, this should be completely legal, since there is a direct public inheritance relationship between View and View2, but cfront rejects it (see my recent posting on this topic for details). If it *is* legal, then I claim my design is "convenient," as I claimed earlier. On the other hand, if it is *not* legal, then assignment of the type I just gave requires an explicit cast, and things become somewhat less convenient. On the other hand, I'd be very interested in an alternative design that is no more complex that what I've got above and that allows me to avoid duplicating code. One constraint is that I don't want to have to modify the protocol class (class View in the above), because View1 and View2 are sharing purely implementation code, so the class View shouldn't have to know anything about it. Scott ------------------------------------------------------------------------------- What do you say to a convicted felon in Providence? "Hello, Mr. Mayor."