zarmer@hplabsz.HPL.HP.COM (Craig Zarmer) (09/15/89)
I have a question about the proper use of inheritance in C++ 2.0. I'm using InterViews, a C++ class library for building user interfaces, but I think my question extends to any use of an aquired class hierarchy. I'm trying to 'wrap' the window class hierarchy in InterViews to add some extra functionality. Though in the case of InterViews I have source and could just hack the root class, I'd prefer not too, and in the general case we usually don't get source. As an example, take the following InterViews classes: class Interactor {public virtual void Draw(); ...}; class PushButton : public Interactor {...}; class ScrollBar : public Interactor {...}; In my own world I have class ApplicObject {...}; I want to add a public member ApplicObject *subject to all Interactors. I then need a type or types that will allow me to generically access subject and any public member of Interactor. Schemes that don't work at all: 1) Add a new class SubjectView that contains an ApplicObject* and make Interactor inherit from it. This would only work if I had InterViews source, which I'm pretending I don't. 2) Create a subclass of every interesting Interactor and add subject. This leaves me with no generic way to reference subject. 3) Create a class SubjectView, and create subclasses of every interesting Interactor by inheriting both from the Interactor subclass and SubjectView. I.e. class SubjectView {public: ApplicObject *subject;}; class MyPushButton : public PushButton, public SubjectView {...}; This gives me generic access to both InterViews and subject, but not at the same time. There is no way to coerce from SubjectView* to Interactor*, and I need to be able to Draw() and access subject. 4) Create SubjectView as above, then create a dummy class inheriting from SubjectView and Interactor to get a common type, then create subclasses inheriting from the common type and InterViews subclasses: class SubjectView {public: ApplicObject *subject;}; class SubjectInteractor : public SubjectView, public Interactor {}; class MyPushButton : public PushButton, public SubjectInteractor {}; This is real close, but MyPushButton will get 2 Interactors. Since I don't have source, I can't make Interview's PushButton inherit virtually from Interactor. Ugly schemes that sort-of work: 5) Define SubjectView: class SubjectView { public: ApplicObject *subject; Interactor *view; ...}; Make subclasses of Interactor classes that inherit from SubjectView too and in their constructors, have them set view to themselves. In other words, give Mom a pointer to Dad via the Child. 6) Forget inheritance; make SubjectView a wholy separate class. Use a hash table to get the relation from Interactors to SubjectViews. Since most companies that develop libraries for sale will not release source, this scenario seems rather common. Is there a proper way to take in a class library and wrap all of the classes with some extra functionality? Thanks, Craig Zarmer zarmer@hplabs.hp.com
vaughan@mcc.com (Paul Vaughan) (09/16/89)
From: zarmer@hplabsz.HPL.HP.COM (Craig Zarmer) I have a question about the proper use of inheritance in C++ 2.0. I'm using InterViews, a C++ class library for building user interfaces, but I think my question extends to any use of an aquired class hierarchy. I'm trying to 'wrap' the window class hierarchy in InterViews to add some extra functionality. Though in the case of InterViews I have source and could just hack the root class, I'd prefer not too, and in the general case we usually don't get source. As an example, take the following InterViews classes: class Interactor {public virtual void Draw(); ...}; class PushButton : public Interactor {...}; class ScrollBar : public Interactor {...}; In my own world I have class ApplicObject {...}; I want to add a public member ApplicObject *subject to all Interactors. I then need a type or types that will allow me to generically access subject and any public member of Interactor. Schemes that don't work at all: 1) Add a new class SubjectView that contains an ApplicObject* and make Interactor inherit from it. This would only work if I had InterViews source, which I'm pretending I don't. 2) Create a subclass of every interesting Interactor and add subject. This leaves me with no generic way to reference subject. 3) Create a class SubjectView, and create subclasses of every interesting Interactor by inheriting both from the Interactor subclass and SubjectView. I.e. class SubjectView {public: ApplicObject *subject;}; class MyPushButton : public PushButton, public SubjectView {...}; This gives me generic access to both InterViews and subject, but not at the same time. There is no way to coerce from SubjectView* to Interactor*, and I need to be able to Draw() and access subject. 4) Create SubjectView as above, then create a dummy class inheriting from SubjectView and Interactor to get a common type, then create subclasses inheriting from the common type and InterViews subclasses: class SubjectView {public: ApplicObject *subject;}; class SubjectInteractor : public SubjectView, public Interactor {}; class MyPushButton : public PushButton, public SubjectInteractor {}; This is real close, but MyPushButton will get 2 Interactors. Since I don't have source, I can't make Interview's PushButton inherit virtually from Interactor. Ugly schemes that sort-of work: 5) Define SubjectView: class SubjectView { public: ApplicObject *subject; Interactor *view; ...}; Make subclasses of Interactor classes that inherit from SubjectView too and in their constructors, have them set view to themselves. In other words, give Mom a pointer to Dad via the Child. 6) Forget inheritance; make SubjectView a wholy separate class. Use a hash table to get the relation from Interactors to SubjectViews. Since most companies that develop libraries for sale will not release source, this scenario seems rather common. Is there a proper way to take in a class library and wrap all of the classes with some extra functionality? You ask a very good question. I've been beating my head against the wall trying to figure this one out for awhile. So far my answer has been: avoid having it come up. Since we are actually making the libraries that I'm worried about, we can just make base classes virtual in anticipation of the problem later on. I really wish you could have both a virtual and nonvirtual derivation from a base class in the inheritance hierarchy and have the virtual one use the nonvirtual one, instead of creating a new one. But in response to your question, I have a partially satisfactory answer, based on your choice #3. "But how do you access methods from both base classes," you say? Answer: cheat, kludge, lie, steal. Actually it's not THAT bad--check out the following code. It compiles and runs with g++ 1.35.1. The basic technique is to use pointers or references of both base class types (that is, one of each base class type). These pointers point at the same object, and therefore that object must derive from both base classes. The trick is that when the assignment is made, the actual type of the object is known and the legality of the assignments is assured. Later, the actual class is not known, but you can access either base class methods through the appropriate pointer. "But, suppose your program is structured so that you don't have access to the actual type when you want to make such an assignment," you say. Well, I haven't covered that yet. I'm still working on it. If you don't find yourself saying that because you don't think it can happen that way, let me know. Also, I suspect you can improve on some of the properties of this technique. #include <stream.h> class LibBase { public: LibBase() { cout << "LibBase Constructor\n";}; foo() {}; }; class LibDerived : public LibBase { public: LibDerived() { cout << "LibDerived Constructor\n";}; }; class MyBase { public: MyBase() { cout << "MyBase Constructor\n";}; bar() {}; }; class MyDerived : public MyBase, public LibDerived { public: MyDerived() { cout << "MyDerived Constructor\n"; }; }; class Client { public: Client(LibBase* lbp, MyBase* mbp) { cout << "Client Constructor\n"; lb = lbp; mb = mbp;}; LibBase* lb; // duplicate pointers, one for each base class MyBase* mb; foobar() { lb->foo(); mb->bar();}; }; main() { MyDerived md; MyBase& mb = md; //duplicate references, one of each base class LibBase& lb = md; lb.foo(); mb.bar(); Client c(&md,&md); c.foobar(); } Paul Vaughan, MCC CAD Program | ARPA: vaughan@mcc.com | Phone: [512] 338-3639 Box 200195, Austin, TX 78720 | UUCP: ...!cs.utexas.edu!milano!cadillac!vaughan
jima@hplsla.HP.COM (Jim Adcock) (09/20/89)
Interesting issue. A user of a derived class D decides that D really should have had a virtual base class B instead a non-virtual B. How does a user decide that without seeing the source? How can a user be sure that D doesn't change the operation of B? --This is far different from a class E whose base B is declared virtual. Which represents a contract by E not to use B in a way that would preclude the base instance "B-part" of E from being used by another class. E would typically only do things to the B-part in the E's constructors and destructors. Thus E uses B by mere aggregation with B -- B's capabilities are not mixed with E's, but rather B's and E's capabilities are orthogonal. In which case B's and E's capabilities should be implemented totally separately: implement the meat of E instead in a class Eprime, and then create E via trivial derivation: class E : public B, public Eprime { E() : B(something), Eprime(somethingelse) {} ~E() {maybesomething();} }; Then, if the user of your classes decides he/she needs something like E but with a virtual B, the user can trivially make such a class: class Evirt : public virtual B, public E { Evirt() : B(something), Eprime(somethingelse) {} ~Evirt() {maybesomething();} }; Or you could provide both flavors, virtual base "Evirt" and non-virtual base "E" to your users to use as they please. But you complain: "I write my Eprime-meat as part of E because my Eprime-part is not really orthogonal to B." Fine, but then the user was never in a situation where having B virtual would have done the user any good. Because E was never written in a way to let the B part be shared by another class.