Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!csd4.milw.wisc.edu!cs.utexas.edu!pp!milano!cadillac!vaughan@mcc.com From: vaughan@mcc.com (Paul Vaughan) Newsgroups: comp.lang.c++ Subject: Re: changing return type of virtual Message-ID: <1993@cadillac.CAD.MCC.COM> Date: 31 Jul 89 19:23:17 GMT References: <1974@cadillac.CAD.MCC.COM> <43468@bbn.COM> Sender: news@cadillac.CAD.MCC.COM Reply-To: vaughan@mcc.com (Paul Vaughan) Organization: MCC VLSI CAD Program Lines: 107 In-reply-to: lpringle@bbn.com (Lewis G. Pringle) Lewis Pringle gives a good example of the issue at hand >Example: > ScrollBar has an data mamber Thumb. Now I want to make a >ScrollBarWithSizeableThumb. I subclass both classes to the right thing, >and add a SetSize() method to thunb (so the ScrollBarWithSIzeableThumb can >reset it). Now the ScrollBar has an fThumb field of type (Thumb*). Do >I use this and always cast? Do I use a new data member fThumb with the new >type and shadow the parent class name? What is the best way to deal with >this kind of problem???? >The problem, it that for data members it is ALMOST ALWAYS DANGEROUS, and that >is why (assumtion) the compiler does not allow it. You see, a base class >pointer could then be used to assign a bad value. >For virtual functions arguments the same danger applies. I've been considering ways that the compiler could verify correct handling of subclassed data members. I can't say that I've thought it out thoroughly, but here goes. The main idea is that the data member cannot be publicly accessable. This insures that only friends and member functions can deal directly with the data member. Now, it seems feasible to me that the compiler could simply verify that friends and members dealt with the data member in a type-safe fashion. This implies that any member function that assumes a type for the data member (that is, assigns a value, copies it via assignment, returns it as a value, passes it as a paramemter, etc.) must be virtual, and that the subclass must have it's own local definition for each such member function (and that the local definitions assign the right type). It also implies that a virtual function that assumes a type for the data member may not be called directly using a parent:: construct. I'm not sure how this would interact with constructors and the rules about how virtual functions act within them. It seems to me that these are all things that the compiler could verify without difficulty, thereby proving the program to be type-save without relying on programmer assertions (casts). The same argument applies for virtual function arguments, as long as the virtual function is protected. The function cannot be called, except from other member functions which are virtual and have a local definition. This might also work for subclassed return types, allowing virtual functions to return subclassed objects or pointers to subclassed objects. Andrew, you mention that there is a problem with type ambiguity if a member function could return a reference to a subclassed type. I don't think this is a problem given the rules outlined here. However, if you change your example to returning a pointer, rather than a reference, I'm not sure that there is a problem even with less restrictive rules. For instance, if you make that change, how does your example differ from this program: #include class A { public: int i; }; class B : public A { public: B() { j = 1;}; int j; }; main() { B* bp = new B; cout << "j = " << bp->j << "\n"; // prints j = 1 as expected A* ap = bp; // loses track of actual type here A a; a = *ap; // copies the A part into an A size space *ap = a; // copies the A part into a B size space cout << "j = " << bp->j << "\n"; // prints j = 1 (unchanged, as expected) delete(ap); // don't know what this does (memory leak?) cout << "works so far\n"; } Here the normal language rules specify that only the A part of *ap gets copied into a. In the reference case, where a reference to some potentially subclassed type is being assigned via a reference to another potentially subclassed type (as in your example), I would expect the target reference to be treated as the type visible at compilation time, (that is like the parent type). The value reference would naturally be treated like the parent type, just as the pointer is in the above program. Reclaiming the extra space and figuring out what to do with calling destructors is more difficult. But then, I'm not sure what happens in the above program when the delete(&a) is executed either. I don't understand the argument about overloaded functions either. Overloaded functions are already defined to only work on the type as visible at compile time. Of course, I see this as a weakness (but an efficient weakness) as compared to true runtime multiple dispatch (ala generic functions in CLOS), but that's a different issue. BTW, this issue is important to me. It comes up a lot when making libraries and toolkits of things for users to put together. 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