craig@gpu.utcs.utoronto.ca (Craig Hubley) (02/20/91)
In article <1991Feb19.191731.4137@pa.dec.com> lattanzi@decwrl.dec.com (Len Lattanzi) writes: >In article <65451@brunix.UUCP> sdm@cs.brown.edu (Scott Meyers) writes: >:In article <607@taumet.com> steve@taumet.com (Stephen Clamage) writes: >:| >imagine a database query: >:| >:| > "from the list of shapes, show me all the circles of diameter > 30" >:| >:| All you have to do is make a virtual function diameter() for all Shapes. Yes, the existing C++ mechanisms provide a way to do this. But there was also a way to do templates with macros, which was prone to errors and abuse. So templates were blessed. I would suggest the same thing is true with this. >:| A function which expects to operate on a class of type Shape may >:| reaonably expect all the functionality of Shape to be present. It may >:| not reasonably expect specific behaviors of classes derived from Shape >:| to be present. >: >:This is of course the right way to go, and most of the time it is a >:workable solution, but there are times when it simply won't do. Unfortunately many of us seem, including Bjarne, seem to be stuck in a mold of believing a general theory, like strong typing or object- oriented hierarchies, and acknowledging exceptions to it instead of reconsidering the theory. So far as I am concerned, strong typing means little in a language with conversion, and hierarchy is not the only blessed way to share specifications or code, as templates prove. >:I personally believe that we need language support for this kind of thing, >:but it would be so prone to abuse and Bjarne is so dead-set against it that >:I don't hold out much hope that it will be seriously considered by the ANSI There are ways to cut the abuse. Consider these possible solutions: PROBLEM accessing features that do not exist on all of the possible types of a passed object, e.g. "is this a circle of diameter > 30 ?" TODAY explicit type tag - inconsistent across user programs - cannot be added to imported classes SOL.1 type switch, similar to value switch - functions specific to more specialized class available in case-arm - class name used to distinguish type of object class_switch x { case circle: if (x.diameter > 30) then { ... } } SOL.2 .class member, similar to .this member - public data member class, read-only - class name usable only in equivalence expression - equivalence rules for derived types if (x.class == circle) && ((circle)x.diameter > 30)) then { ... } SOL.3 conditional cast, similar to catch - if cast is illegal, no action, entire expression returns false - if cast is legal, it occurs and rest of block is executed - requires new syntax (here represented as double parentheses) if ( ((circle)) x.diameter > 30) then { ... } SOL.4 as above, only testing for presence of members, not name of classes - avoids dependencies on current class/member structure - works for inherited members The above would require anyone who really intended to abuse the information to continue to keep his/her own type tags, so this abuse would not be "blessed". In fact, I think opportunities for abuse would markedly diminish if one of the above mechanisms was added. After all, 95% of your need for a type tag would be covered, and why would you invent your own mechanism for that abusive 5% ? However, if you have to invent your own anyway, or use a base class library that has its own, you are more likely to take advantage of it and increase dependencies in your code. At least IMHO. >:committee. Instead, I think that compiler vendors will add proprietary >:support for it, and there will be a market free-for-all. I know that at This is to be avoided at all costs. Arg, a grass-roots rebellion against the language definition... ye gods. Look what this did to COBOL and Pascal. >:least one compiler vendor already has implemented such a facility for >:getting type information at runtime. I hope whatever they do is supportable as a standard, because you can bet it'll end up as one. >:The bottom line is that virtual functions are wonderful things and can >:almost always be used to achieve what you want, but there are times when >:you truly *do* need to get type information as you run. I agree, and so do designers of all other o-o languages. It just isn't a controversy, except for Bjarne it seems. At least I haven't seen any evidence of someone else saying you should never see the type of an object. I hope ANSI C++ doesn't end up as a half-baked collision of object-oriented dogma and C dogma. The mere fact that compiler developers are including it is a strong sign of user demand, and it is obviously not ignorant demand. >:Scott > >This is especially true for expanding library interfaces. I wished for Frankly I think Bjarne's concern is entirely misplaced. I would worry far more about the dozens of weird mechanisms for getting at type information than about non-robust uses of it, or about the possibility that someone will invent their own style of virtual functions given that information. Although I recently proposed a different kind of virtual function with different overriding rules, I would rather work within, or change, the language than invent such a scheme for use only in my own programs. However, I can't be stopped from doing it, even by hiding the type tag. So what is gained by hiding it, I don't know. >an "interactive(istream&)" predicate but had my hands tied. I could >either compare pointer-to-members to guess at class type (boo! hiss!) >or use iostream state variables to record this information. Something >like a property-list per object is probably the desire of most lisp hackers. Speaking as an ex-Lisp hacker, right on. But this needn't wait until runtime to be resolved, and compromise C++'s precious efficiency. There are such things as optimizing compilers, which C-heads usually have never heard of because the language's low-level constructs are so pervasive and hard to optimize. I've heard C described as "welfare for mediocre compiler writers", and after using C for 7 years I tend to agree. > Len Lattanzi (Migration Software Systems Ltd 408 452 0527) <len@migration.com> The next thing I expect compiler writers to do is hack the linker so that new versions of compiled-in libraries can be patched in at runtime. They gotta go into the object code, hack out calls to the subroutines that changed and replace 'em with a call to a jump table into the new library. Compatible with C ? Hah ha hahahah... but there are definitely a lot of people, even on the ANSI committee, that couldn't care less about C. And I agree with 'em. Who actually compiles C sources under C++ anyway ? -- Craig Hubley "...get rid of a man as soon as he thinks himself an expert." Craig Hubley & Associates------------------------------------Henry Ford Sr. craig@gpu.utcs.Utoronto.CA UUNET!utai!utgpu!craig craig@utorgpu.BITNET craig@gpu.utcs.toronto.EDU {allegra,bnr-vpa,decvax}!utcsri!utgpu!craig
chip@tct.uucp (Chip Salzenberg) (02/21/91)
According to craig@gpu.utcs.utoronto.ca (Craig Hubley): >PROBLEM accessing features that do not exist on all of the possible types of > a passed object, e.g. "is this a circle of diameter > 30 ?" > >TODAY explicit type tag > - inconsistent across user programs > - cannot be added to imported classes Bzzt. You forgot: TODAY.2 virtual member function that does the operation or reports failure and/or raises an exception. - uses language features already available - doesn't require client changes when class hierarchy changes >SOL.2 .class member, similar to .this member This solution would require a unique program-wide integer that would represent each class. (Can you say Objective-C? Ugh.) It would also impose the space overhead of virtual functions on classes that do not need it. It would further make programs dependendent on the details of inheritance. ("Let's see, I want all subclasses of foo except bar, um, they're larry, moe, curly..." Double ugh.) >SOL.3 conditional cast, similar to catch Eliminates the knowledge-of-inheritance problem, but cost-of-virtual and unique-class-id problems are still there. >SOL.4 as above, only testing for presence of members, not name of classes How do you know that "circle.size()" and "building.size()" are in any way related to each other? You don't. Member name is meaningless without some indication of which "size()" is the Right One. So once you figure out which one is the Right One, make it virtual. Presto. >It just isn't a controversy, except for Bjarne it seems. At least I >haven't seen any evidence of someone else saying you should never see >the type of an object. Count me in with Bjarne. You, the programmer, never really want to know the type. You may think you want to know the type, but why? So you can know if a feature is available. So make the feature a virtual function and be done with it. >I hope ANSI C++ doesn't end up as a half-baked collision of object-oriented >dogma and C dogma. Why change now? :-) >The mere fact that compiler developers are including it is a strong >sign of user demand, and it is obviously not ignorant demand. "Obviously?" It's not obvious to me. >There are such things as optimizing compilers, which C-heads usually have >never heard of because the language's low-level constructs are so pervasive >and hard to optimize. I know all about optimizers, thank you very much. They are utterly irrelevant to the issues of type knowledge. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber (with reference to the upage bug in Interactive UNIX and Everex ESIX)
dsouza@optima.cad.mcc.com (Desmond Dsouza) (02/25/91)
This topic does come up quite often, doesn't it? In article <27C2D973.3C1B@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: > Bzzt. You forgot: > > TODAY.2 virtual member function that does the operation or reports > failure and/or raises an exception. > - uses language features already available > - doesn't require client changes when class hierarchy changes > > >SOL.2 .class member, similar to .this member > if (x.class == circle) && ((circle)x.diameter > 30)) then { > > This solution would require a unique program-wide integer that would > represent each class. (Can you say Objective-C? Ugh.) It would also > impose the space overhead of virtual functions on classes that do not > need it. It would further make programs dependendent on the details > of inheritance. ("Let's see, I want all subclasses of foo except bar, > um, they're larry, moe, curly..." Double ugh.) > This only requires that (x.class) be of type, say, "ClassId", which supports "ClassId == ClassId" for class identity, and possibly "ClassId <= ClassId" for sub-class checks. The user does not need to worry about details of inheritance as this is done by the ClassId type. ClassId could also contain lots of other incredibly useful information in general. ClassId may be implemented as a string encoding the object's class name as well as the class names of all its ancestors, or as one of many possible alternate encodings of hierarchies. It does not require a unique program-wide integer per class. It is reasonable for x.class to be a virtual function call ONLY if class X already has virtual functions (why else would you have a Circle in a list of Shapes?). Otherwise, it could be just a regular member function call. Hence, you do not have to add virtual-ness to otherwise non-virtual classes. > > >It just isn't a controversy, except for Bjarne it seems. At least I > >haven't seen any evidence of someone else saying you should never see > >the type of an object. > > Count me in with Bjarne. You, the programmer, never really want to > know the type. You may think you want to know the type, but why? So > you can know if a feature is available. So make the feature a virtual > function and be done with it. There are a lot of cases where free access to type information would be better replaced with virtual functions, BUT NOT ALL. Here are a few examples where you need to know the type of an object: 1. Persistent objects: When reading in one of these from disk, you need to know what constructor to call. Hence you need to encode in the persistent image the ClassId of the object. 2. You have an interactive system where a mouse-weilding user dynamically configures objects together. You need to check the configuration for validity, which needs many of the same type-relationship checks that the compiler would perform, were it statically analyzing program text which configured the same objects. 3. Almost any application related to a programming environment: debuggers, smart re-compilers, ... > -- > Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> > "It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber > (with reference to the upage bug in Interactive UNIX and Everex ESIX) The compiler has access to almost all of this information at compile time. If the above 3 examples reflect a legitimate need, it seems unreasonable for the user to worry about them all over again. Also, C++ cast restrictions on virtual-base classes (cannot cast from virtual base to derived) could be removed if the compiler made some of this information available at run-time! Desmond D'Souza MCC. -- ------------------------------------------------------------------------------- Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com | Phone: [512] 338-3324 Box 200195, Austin, TX 78720 | UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!dsouza
chip@tct.uucp (Chip Salzenberg) (02/26/91)
According to dsouza@optima.cad.mcc.com (Desmond Dsouza): >... only requires that (x.class) be of type, say, "ClassId", which >supports "ClassId == ClassId" for class identity, and possibly >"ClassId <= ClassId" for sub-class checks. ... It does not require >a unique program-wide integer per class. I stand corrected. >It is reasonable for x.class to be a virtual function call ONLY if >class X already has virtual functions ... Hence, you do not have to >add virtual-ness to otherwise non-virtual classes. I stand corrected^2. It wouldn't be the efficiency hog I had claimed. Nevertheless, I have a hard time supporting the feature. Every time I hear "typeof(foo)" I see masses of poorly-written Smalltalk: "foo isTypeOf: OrderedCollection or: foo isTypeOf: UnorderedCollection..." >Here are a few examples where you need to know the type of an object: "Need?" That's a red flag for me... :-) >1. Persistent objects: When reading in one of these from disk, you > need to know what constructor to call. Hence you need to encode in > the persistent image the ClassId of the object. Presumably, a well-designed class hierarchy will use a virtual function to store objects; and a virtual function by definition will already know the exact type of the object it is storing. >2. You have an interactive system where a mouse-weilding user > dynamically configures objects together. You need to check the > configuration for validity ... ClassID would be a useful tool in such code, but it is by no means necessary. For example, each class can have virtual functions along the lines of "int can_be_owned()". And do you want J. Random Coder making decisions like "if x is a Widget, but not a FooWidget..." >3. Almost any application related to a programming environment: > debuggers, smart re-compilers, ... I can see it for debugging. But the extra cost of the feature is not something I'd want to inherit permanently in my delivered executable. And I must say that debugging alone is a little thin as justification for a language feature with run-time costs. >C++ cast restrictions on virtual-base classes (cannot cast from >virtual base to derived) could be removed if the compiler made some >of this information available at run-time! But you can always write a virtual function that does the cast. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber (with reference to the upage bug in Interactive UNIX and Everex ESIX)
jimad@microsoft.UUCP (Jim ADCOCK) (02/26/91)
In article <DSOUZA.91Feb24173454@optima.cad.mcc.com> dsouza@optima.cad.mcc.com (Desmond Dsouza) writes: | |This topic does come up quite often, doesn't it? Yep. Seems like the C++ world is solidly in two camps on this issue. One camp says any notion of run-time type is bad, and we're not going to support it. The other camp says that in some situations some notions of run-time type are useful, and C++ ought to have some good way to support it. My claim is that if some notions of run-time type are not in the language, then a lot of C++ vendors are simply going to go out and implement their own, incompatible notions of run-time type. I suggest that run-time type info can be specified as part of the language, can be done in a way that classes that don't want to use such a feature pay no price, and that run-time type info can be implemented in compilers at fairly low cost. Therefore I suggest that compile vendors ought to agree to get together and decide how this should be done. Leave it to the end programmer whether they want to use the feature or not, just like today we leave it to the end user whether they decide to use multiple inheritence or not. I further claim that problems with run-time type demonstrate a general problem in c++: today there is no good way to specify features in a base class that are to be implemented in each derived class. You'd think there'd be some way to use templates for such problems, but today that doesn't seem true. The best one can do today appears to be the macro class declaration/definition hack: DECLARE_CLASS(derived, base) .... }; DEFINE_CLASS(derived, base) And, like always, I claim any common, recurring theme that programmers consistently have to address via the preprocessor represents a deficiency in the language.
wmm@world.std.com (William M Miller) (02/26/91)
jimad@microsoft.UUCP (Jim ADCOCK) writes: > I suggest that run-time type info can be specified as part of the language, > can be done in a way that classes that don't want to use such a feature > pay no price, and that run-time type info can be implemented in compilers > at fairly low cost. We agree. In fact, I submitted an abstract for a paper for the April USENIX C++ Conference describing exactly such a design; however, the program committee decided that a design-only paper without implementation and user experience did not meet their acceptance criteria. > Therefore I suggest that compile vendors ought to agree to get together and > decide how this should be done. That was the reason for submitting the paper for publication in the first place. We're still interested in discussions with other compiler vendors to see if a de facto standard for runtime type information can be established. Perhaps we can get some of the interested parties together at the upcoming X3J16 meeting in Nashua. -- William M. Miller, Glockenspiel, Ltd. wmm@world.std.com
dsouza@optima.cad.mcc.com (Desmond Dsouza) (02/27/91)
In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: > >Here are a few examples where you need to know the type of an object: > > "Need?" That's a red flag for me... :-) It is a red flag for me too. And lots of red flags mean it is quite possibly a legitimate shortcoming. > >1. Persistent objects: When reading in one of these from disk, you > > need to know what constructor to call. Hence you need to encode in ^^^^^^^^^^^^^^ > > the persistent image the ClassId of the object. > > Presumably, a well-designed class hierarchy will use a virtual > function to store objects; and a virtual function by definition will > already know the exact type of the object it is storing. Yes, but it needs to make it _explicitly_ available in the binary image. Retrieving and re-constructing persistent objects is where you *need* explicit type information. Virtual functions dont help now because you do not have an existing object to dispatch off. Storing objects is relatively easy, e.g. with a virtual function which writes out some ClassId information. Hence, your objects must have a way to access (and write out) their true type. Ditto for transferring objects across address spaces, networks, etc. > >2. You have an interactive system where a mouse-wielding user > > dynamically configures objects together. You need to check the > > configuration for validity ... > > ClassID would be a useful tool in such code, but it is by no means > necessary. For example, each class can have virtual functions along > the lines of "int can_be_owned()". And do you want J. Random Coder > making decisions like "if x is a Widget, but not a FooWidget..." Solutions like "virtual int can_be_owned()" are an incredible implementation overhead when you are dealing with combinations of object classes and relationships between them. e.g. Can an A be plugged into a B in the slot x ? How about an A1 into slot z of a B1? Why not offer the type checking facility and teach J. Random Coder when _not_ to use it ? > >3. Almost any application related to a programming environment: > > debuggers, smart re-compilers, ... > > I can see it for debugging. But the extra cost of the feature is not > something I'd want to inherit permanently in my delivered executable. > And I must say that debugging alone is a little thin as justification > for a language feature with run-time costs. The cost, as you admitted, can be quite manageable. The primary problem, as Stroustrup points out, is pick an acceptable amount of functionality for 'ClassId'. Check out some papers on "reflection" in languages to see just how powerful classes-as-objects _can_ be. In fact, persistence, dynamic configuration and debugging can be all viewed as "reflective" capabilities. Consider the proposed (now accepted) Exception Handling scheme for C++. The statement: catch (ErrorClass& o) { .... } is supposed to 'catch' a deeply nested call to: throw ErrorClass("someError"); Any implementation of this involves a run-time type check, including class derivation, on the object being thrown. Can you say 'Meta-class'? Most static class variables and static class functions (explicit or implicit) can be considered members of a corresponding class object. > >C++ cast restrictions on virtual-base classes (cannot cast from > >virtual base to derived) could be removed if the compiler made some > >of this information available at run-time! > > But you can always write a virtual function that does the cast. True. It is painful, error-prone, and the compiler can do a much better job. > Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> Desmond. -- ------------------------------------------------------------------------------- Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com | Phone: [512] 338-3324 Box 200195, Austin, TX 78720 | UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!dsouza
glenn@huxley.huxley.bitstream.com (Glenn P. Parker) (02/27/91)
In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: > According to dsouza@optima.cad.mcc.com (Desmond Dsouza): >>1. Persistent objects: When reading in one of these from disk, you >> need to know what constructor to call. Hence you need to encode in >> the persistent image the ClassId of the object. > > Presumably, a well-designed class hierarchy will use a virtual > function to store objects; and a virtual function by definition will > already know the exact type of the object it is storing. Check that article again. Desmond is talking about reading in a persistent object, not writing it out. I believe this is the most common (and most easily explained) situation that requires "extra" type information. -- Glenn P. Parker glenn@bitstream.com Bitstream, Inc. uunet!huxley!glenn 215 First Street BIX: parker Cambridge, MA 02142-1270
peterson@choctaw.csc.ti.com (Bob Peterson) (02/27/91)
In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to dsouza@optima.cad.mcc.com (Desmond Dsouza): >... >>1. Persistent objects: When reading in one of these from disk, you >> need to know what constructor to call. Hence you need to encode in >> the persistent image the ClassId of the object. > >Presumably, a well-designed class hierarchy will use a virtual >function to store objects; and a virtual function by definition will >already know the exact type of the object it is storing. > This view of how to implement persistence seems narrow. Researchers and vendors have taken a number of implementation approaches, including the one you mention. The details of the various implementations vary widely, but few depend on virtual functions to supply type information. In all cases some indication of the actual type of the stored object must be available when the object is recovered. Of course, this implies type information must be available when an object is to be stored. I daresay many of us working in the area of persistence would welcome a standard way of encoding and accessing type information. Actually the problem generalizes quite readily to exporting and importing an object from and to an address space, regardless of the purpose. An obvious purpose, other than storage, is for interprocess communications. >... >-- >Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> >"It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber > (with reference to the upage bug in Interactive UNIX and Everex ESIX) Bob Peterson Compuserve: 70235,326 Expressway Site, Texas Instruments Internet: peterson@csc.ti.com North Building, P.O. Box 655474, MS238 Landline: (214) 995-6080 2nd Floor, Dallas, Texas, USA 75265 CSC Aisle C3
chip@tct.uucp (Chip Salzenberg) (02/28/91)
According to dsouza@optima.cad.mcc.com (Desmond Dsouza): >Retrieving and re-constructing persistent objects is where you *need* >explicit type information. Virtual functions dont help now ... Of course they don't help; but explicit type information doesn't help, either. The language type system is inoperative when dealing with objects that have been "frozen" into dead arrays of bytes. >Storing objects is relatively easy, e.g. with a virtual function which >writes out some ClassId information. Hence, your objects must have a >way to access (and write out) their true type. When a virtual function is invoked, the type is implied, unless the programmer has made the mistake of allowing a virtual function to be inherited by a derived class that should be frozen in a distinct way from its parent. >Solutions like "virtual int can_be_owned()" are an incredible >implementation overhead when you are dealing with combinations of >object classes and relationships between them. > >e.g. Can an A be plugged into a B in the slot x ? How about an A1 into >slot z of a B1? The problem as described is inherently complex. The complexity of a solution is neither created nor worsened by a solution of the can_be_owned() type. >Why not offer the type checking facility and teach J. Random Coder >when _not_ to use it ? I agree with this approach. After all, if I opposed features purely on the basis of possible misuse, then I wouldn't be using C++. :-) So in the end, my opposition isn't to the feature, but to the attitude manifested by some that it's practically necessary for OOP. It's not. ``isKindOf is the OOP equivalent of goto.'' --me >Consider the proposed (now accepted) Exception Handling scheme for >C++. The statement: > catch (ErrorClass& o) { > .... > } >is supposed to 'catch' a deeply nested call to: > throw ErrorClass("someError"); Unless I misunderstand the implementation issues, name equivalence can solve this problem without resorting to dynamic type information, and also without incurring run-time overhead that dynamic type checking would likely impose. > > But you can always write a virtual function that does the cast. > >True. It is painful, error-prone, and the compiler can do a much better job. My experience is not so negative, though I grant that I'd prefer the compiler to more of my work. (Always!) -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber (with reference to the upage bug in Interactive UNIX and Everex ESIX)
chip@tct.uucp (Chip Salzenberg) (02/28/91)
According to peterson@choctaw.csc.ti.com (Bob Peterson): >In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>Presumably, a well-designed class hierarchy will use a virtual >>function to store objects ... > >Researchers and vendors have taken a number of implementation approaches >... The details of the various implementations vary widely, but few depend >on virtual functions to supply type information. Is there any reason to avoid virtual functions for object storage? I had not envisioned a virtual function to return type information, but rather a virtual function to actually _do_the_work_, so no one outside of that function need know the actual type of the object stored. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber (with reference to the upage bug in Interactive UNIX and Everex ESIX)
vaughan@puma.cad.mcc.com (Paul Vaughan) (03/01/91)
I work on an application that has objects that know their type (implementation based on the NIHCL approach). This application also has many classes which do not have type information. I see this as an expression of the flexibility of C++--if you need type information, you can program it, otherwise you're not burdened with its overhead. However my attitude about this is not inflexible. I believe it would make sense to include type related functions for classes which have virtual functions as a standard part of the language. Overhead could be restricted to a per class basis, rather than a per object basis. Certain limitations, such as casting down from a virtual base class, could then be overcome. Compatibility with C would not be affected. Compatibility with existing code would be minimally affected. I believe that the need for this sort of information on objects that have virtual functions is pervasive enough to warrant this minimal overhead. I believe that compiler supported implementations could be substantially better than user programmed implementations. While I don't have any good suggestions for how this might be supported syntactically, I believe that a reasonable solution can be found. In short, I believe that it makes sense to recognize "classes with virtual functions" as an important concept worthy of a name and special functionality. -- 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 ---------------------------------------------------------------------------------- I spent from $3 to $10 today to pay interest on the national debt. How about you? ----------------------------------------------------------------------------------
Reid Ellis <rae@utcs.toronto.edu> (03/01/91)
Desmond Dsouza <dsouza@optima.cad.mcc.com> writes: >Consider the proposed (now accepted) Exception Handling scheme for >C++. The statement: > catch (ErrorClass& o) { > .... > } >is supposed to 'catch' a deeply nested call to: > throw ErrorClass("someError"); > >Any implementation of this involves a run-time type check, including >class derivation, on the object being thrown. Can you say 'Meta-class'? This is simply overloading based on argument type. Just like having methods named "foo(Tbase &)" and "foo(Tderived &)". No "Meta-class" required. Reid -- Reid Ellis 176 Brookbanks Drive, Toronto ON, M3A 2T5 Canada rae@utcs.toronto.edu || rae%alias@csri.toronto.edu CDA0610@applelink.apple.com || +1 416 446 1644
chip@tct.uucp (Chip Salzenberg) (03/02/91)
According to vaughan@puma.cad.mcc.com (Paul Vaughan): >In short, I believe that it makes sense to recognize "classes with >virtual functions" as an important concept worthy of a name and >special functionality. I consider this a reasonable stand. In particular, "classes with virtual functions" should be identical to "classes with virtual destructors." I would go so far as to make illegal a class with virtual functions but a non-virtual destructor. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip>
jbuck@galileo.berkeley.edu (Joe Buck) (03/02/91)
In article <27CD159D.6581@tct.uucp>, chip@tct.uucp (Chip Salzenberg) writes: |> I had not envisioned a virtual function to return type information, |> but rather a virtual function to actually _do_the_work_, so no one |> outside of that function need know the actual type of the object |> stored. You're still missing the point, Chip. I'm generally against functions to return type information, but there are places where you need to do something of this type. Sure, you can have a virtual function to store an object on disk. But how are you going to read it back? There is nothing on the disk but data. A similar problem occurs when inter process communication is required. The system designer must provide some scheme to allow the type to be determined. The interesting thing, though, is that the solution many propose to the problem of determining the type doesn't address this situation at all! If, say, I provide an automatically generated virtual function to return the type somehow, that function won't tell me what's on the disk either. -- Joe Buck jbuck@galileo.berkeley.edu {uunet,ucbvax}!galileo.berkeley.edu!jbuck
rfg@NCD.COM (Ron Guilmette) (03/03/91)
I am cross-posing this response to comp.lang.c++ since it contains information about a general problem that many C++ programmers (and not just ones who are worried about the evolving standard) are concerned about. In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to dsouza@optima.cad.mcc.com (Desmond Dsouza): >>Here are a few examples where you need to know the type of an object: > >"Need?" That's a red flag for me... :-) > >>1. Persistent objects: When reading in one of these from disk, you >> need to know what constructor to call. Hence you need to encode in >> the persistent image the ClassId of the object. > >Presumably, a well-designed class hierarchy will use a virtual >function to store objects; and a virtual function by definition will >already know the exact type of the object it is storing. As Joe Buck pointed out, storing (or transmitting) an object is *not* a problem which requires any sort of special identification of the type of an object, however retrieving (or receiving) an hunk of data which represents the previous contents of some unknown type of thing requires us to use some sort of agreed upon scheme whereby the transmitter sends some unique code with the data block to indicate to the receiver what type of C++ object the transmitted data came from. Assuming that both the transmitting program and the receiving program are written in C++, and assuming that we want the receiving program to perform (or to fake) the re-construction of the originally transmitted object, we need to have some set of globally unique "type codes" (which the transmitter and the receiver must agree upon). We faced exactly this problem when I was working at MCC on the ES-kit project. We had a distributed multiprocessor system within which we wanted to be able to migrate objects and to send "messages" (i.e. member function calls) from one processing node to an object residing on a different processing node. The solution we came up with was not terribly elegant. We ended up hacking the compiler (g++) to get it to provide the actual string representation of the class name to the OS kernel routine which was responsible for forwarding messages between nodes. The kernel then had to do a lookup of the class name string within a table of all class-name string known to the system in order to get a globally unique 32-bit integer valued "code" for that particular class type. This code was then shipped across the mesh as a part of the inter-node "packet" representing the "message" to the remote object. A much cleaner solution would have been to enlist the compiler & linker to assign the globally unique "type codes" up front, prior to run-time. This would have allowed us to avoid the (very expensive) table lookups which we did within the kernel for each transmitted message. Ada implementors are familiar with the problem of providing "globally unique" identifying codes for things. They face the same problem when they go to implement Ada exceptions. Various aspects of Ada effectively create a requirement for an internal set of globally unique identification "codes" for all of the Ada exceptions declared throughout an Ada entire program. In all Ada implementations I know of, these "globally unique" codes for declared Ada exceptions are, in effect, generated at link-time by the linker. For each Ada exception declared within an entire Ada program the Ada compiler generates one small (word sized) artificial variable. When it subsequently needs a unique code for the given exception, it simply uses the address of the associated "dummy" variable as the globally unique "code" for the given exception. Fortunately, the linker see to it that all of these "dummy" variables get allocated to different memory locations (during linking) so that the addresses of these exceptions (i.e. their "codes") are indeed globally unique. An identical scheme could be used to assign globally unique integer codes to each class type within an entire (linked) C++ program. For each class type declaration compiled, a C++ compiler (or translator) could generate a "dummy" variable with a particular (specially mangled) name. The address of that dummy variable could then be used as a globally unique identifier for the class type itself. For example, given: class C { /... }; void *vp; void example () { vp = typeof (class C); } A C++ translator could easily generate: struct C { //... }; int __C__typeof_dummy; void *vp; void __exampleV () { vp = (void *) &__C__typeof_dummy; } This approach would work for C++ *translators* only so long as they are connected to underlying C compilers which allocate uninitialized variables (such as "__C__typeof_dummy") to "common". ANSI C does not allow this practice, but virtually all K&R C compilers and many "ANSI" C compilers still do it anyway. -- // Ron Guilmette - C++ Entomologist // Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg // New motto: If it ain't broke, try using a bigger hammer.
craig@gpu.utcs.utoronto.ca (Craig Hubley) (03/03/91)
In article <27CD159D.6581@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to peterson@choctaw.csc.ti.com (Bob Peterson): >>In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>>Presumably, a well-designed class hierarchy will use a virtual >>>function to store objects ... >> >>Researchers and vendors have taken a number of implementation approaches >>... The details of the various implementations vary widely, but few depend >>on virtual functions to supply type information. > >Is there any reason to avoid virtual functions for object storage? The need to derive all "storable" types from a given base. Among other problems, you can't store an int or a char* that way. If you are using a single base class library, you can define store() and retrieve() in that base. But this is not flexible enough for most OODBs. A more common approach is to define keywords like "persistent" that can be applied to any object. This requires preprocessing outside the definition of C++. >I had not envisioned a virtual function to return type information, >but rather a virtual function to actually _do_the_work_, so no one >outside of that function need know the actual type of the object >stored. This works so long as you are prepared to write a new function for each storable type. This is not what I expect from an OODB. -- Craig Hubley "...get rid of a man as soon as he thinks himself an expert." Craig Hubley & Associates------------------------------------Henry Ford Sr. craig@gpu.utcs.Utoronto.CA UUNET!utai!utgpu!craig craig@utorgpu.BITNET craig@gpu.utcs.toronto.EDU {allegra,bnr-vpa,decvax}!utcsri!utgpu!craig
chip@tct.uucp (Chip Salzenberg) (03/05/91)
According to craig@gpu.utcs.utoronto.ca (Craig Hubley): >In article <27CD159D.6581@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>Is there any reason to avoid virtual functions for object storage? > >The need to derive all "storable" types from a given base. Among other >problems, you can't store an int or a char* that way. I wouldn't expect an OODB to be able to store a |char *|, since it has no way to know what it's pointing at and/or who "owns" it. >>I had not envisioned a virtual function to return type information, >>but rather a virtual function to actually _do_the_work_, so no one >>outside of that function need know the actual type of the object >>stored. > >This works so long as you are prepared to write a new function for each >storable type. This is not what I expect from an OODB. Surely you would in practice distinguish between "some entity needs to write it" and "_I_ need to write it". Any OODB worth the floppy it's distributed on would generate such a function automatically. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "All this is conjecture of course, since I *only* post in the nude. Nothing comes between me and my t.b. Nothing." -- Bill Coderre
chip@tct.uucp (Chip Salzenberg) (03/05/91)
According to rfg@NCD.COM (Ron Guilmette): >... retrieving (or receiving) an hunk of data which represents the >previous contents of some unknown type of thing requires us to use >some sort of agreed upon scheme whereby the transmitter sends some >unique code with the data block to indicate to the receiver what >type of C++ object the transmitted data came from. In my mind, the only reasonable choice for such a unique code is the class name itself. The C++ language depends on name equivalence, so user code may also depend on name equivalence without problems. >The [messaging] kernel then had to do a lookup of the class name string >within a table of all class-name string known to the system in order to >get a globally unique 32-bit integer valued "code" for that particular >class type. Why not use the class name itself? >A much cleaner solution would have been to enlist the compiler & linker >to assign the globally unique "type codes" up front, prior to run-time. >This would have allowed us to avoid the (very expensive) table lookups >which we did within the kernel for each transmitted message. A string lookup though a set of strings fixed at compile time needs to be rewritten if it is "very expensive." -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "All this is conjecture of course, since I *only* post in the nude. Nothing comes between me and my t.b. Nothing." -- Bill Coderre
bill@robots.oxford.ac.uk (Bill Triggs) (03/05/91)
I have been pleasantly surprised by the basic similarity of so many of the type-tagging schemes proposed during this discussion: perhaps those of us who do recognise the need for such things *CAN* agree on a partial standard until proper language support is available. The principal applications of type information seem to be: (i) Testing whether a Base& or Base* reference actually references a Derived instance, ie whether a (Derived) cast is legal. Type info is *essential* for safe down-casting. The alternative of pushing all Derived functions (and implicitly members) into the Base -- either as full implementations or as empty or error-calling stubs -- is simply laughable, although it has been suggested seriously enough by several people during this discussion. (ii) Creation of an instance of an implicitly specified class. For example when reading a persistent object from disk, remotely creating an object across a network, parsing an object- specifying input text, or simply creating a local heap object whose exact type is better not known at compile-time. It seems that an appropriately global `class-designator' is indispensable for this, whether string or global address or global enumeration constant, and that associated with this must be appropriate `static Class* Class::' or `friend Class*' new_object() or read_object(), parse_object() functions. The association can be (a) via an explicit switch() on the class-designator (which must then be translatable to an enum or const int type) (b) via a more dynamic association mechanism such as a hash table (c) the class-designator can be a reference to a class-type record for the class, which contains the appropriate functions. We all know the antipolymorphic evils of building explicit type switches into our code and hashing seems unnecessarily complicated, so (c) seems the best bet: ie a `class ClassType' instance for each class, acting as a `static function vtable'. NB: conditional casts handle only (i) above and are NOT enough by themselves. A `standard' type mechanism ought to meet the following requirements (at least): (i) Type functions must obviously be lightweight virtual member functions for most purposes. Given existing virtual functions the per-instance overhead for type info should be nil. Non-virtual type functions are not very useful, but might be allowed for small classes without virtual functions. (ii) Derivation as well as exact type must be testable --- via `isa()', 'isakindof()' or `object >= type' functions --- and this must work with multiple inheritance (ie it must detect all potential cast-ability). C++-style type forests must be supported. It is helpful for the isa() function to return (void*)this if casting is possible, and 0 otherwise so that CLASS* foo = (CLASS*)isa(CLASS::Type) is possible. (iii) Switch()-ing on type ought to be possible. For this type must be convertible to a compile-time-const int. Type must at least be easily convertible to and from an external, global, time-invariant const for network/disk-based applications, with little risk of clashes between different types. (iv) Type ought to be convertible to a record containing appropriate static object-creation functions (new_object(), read_object(), parse_object()), and other useful information such as class name and schema descriptors. (v) Even though there is no formal language support, type-ing a class should be easy & transparent. A single comprehensible macro call per class would be ideal. (i),(ii) and (v) are straightforward. Unfortunately, (iii) and (iv) are difficult to reconcile because some C++ implementations are very fussy about case label const-ness: in particular g++ (1.39.0) will not accept (casts of) link-time constants such as const struct or string addresses as labels. Given that switches (which work only on exact type) are probably undesirable anyway, I would vote for type-as-ClassType-record rather than type-as-enum-int. The record could always contain an enum if desired. Of course I wouldn't have bothered to discuss all this if I didn't have a barrow to push, so here is my sacrificial offering for the putative standard. It is obviously similar to many others that have been mentioned, but it seems to fit the requirements a little bit better than most (IMHO). I declare it public domain, not subject to patent (at least by me), and free to all. There is no guarantee whatsoever, but IMHO it feels `right' and it has worked well for me for a few months now!. Comments and unencumbered PD alternatives are solicited for comparison. The class ClassType is application dependent, application defined, and irrelevant to the type-checking mechanism. It probably contains things like a class-name string or enumeration const class-token, void* (*)() pointers to `static Class* Class::' or `friend Class*' object creation and reading functions, and class schema information. I see no hope of general agreement on the real contents of this, but an agreed ClassType base class stub might be useful so that types in merged applications don't clash fatally (the type system would then allow safe casting to the true ClassType-type on a per-instance basis, if ClassType itself had a type!). ======================================= // V1.0 30/X/90 -*-C++-*-; Bill Triggs (bill@robots.ox.ac.uk) Oxford AGV project. // type function macros for dynamically-typed classes #ifndef _typefunctions_h #define _typefunctions_h 1 #pragma once #include "ClassType.h" // For (obj.type() == CLASSNAME::Type) comparisons ClassType should define: // inline int ClassType::operator==(const ClassType& t) const { return (this == &t); } // inline int ClassType::operator!=(const ClassType& t) const { return (this != &t); } #define rootclass(virtual) typefunctions( ,virtual) #define subclassof(parent) typefunctions(|| parent::isa(t), ) #define subclassof2(p1,p2) typefunctions(|| p1::isa(t) || p2::isa(t), ) #define subclassof3(p1,p2,p3) typefunctions(|| p1::isa(t) || p2::isa(t) || p3::isa(t), ) #define typefunctions(or_isa_parent,virtual) \ public: \ static const ClassType Type; \ virtual const ClassType& type() const { return Type; } \ virtual const void* isa(const ClassType& t) const \ { return ((&t == &Type) or_isa_parent) ? this : 0; }\ #endif _typefunctions_h ======================================= Use this as follows: //--- header files -- one macro per class class Root { rootclass(virtual); // root class virtual type fns public: .... }; class Tiny { rootclass(inline); // not-so-useful: non-virtual root class type fns public: .... }; class Derived: public Root { subclassof(Root); // derived class type functions public: .... }; class Derived2: public Root, Tiny { subclassof2(Root,Tiny); // multiply derived class type functions public: .... }; //--- implementation files -- a ClassType CLASS::Type per class static const ClassType CLASS::Type(.....); //--- application files -- if (foo.type() != Root::Type) {...} if (foo.isa(Derived::Type)) { ((Derived)foo).bar(); } cout << foo.type().classname; ================================= For those who like switch()-ing on type, or need a global enum-type constant for network or disk-based type designation, I also suggest the use of `meaningful 4-char tokens' as below. Not a foolproof scheme, but a lot safer than an enum with several people adding to it in different files. Easier to debug too, especially with a simple routine to print Tokens as strings. The quotes are a bit messy but there's not a lot of choice if you need a compile-time constant. ================================== // V0.0 24/IX/90 -*-C++-*-; Bill Triggs (bill@robots.ox.ac.uk) Oxford AGV project. // parser token (4 char mnemonic "enumeration type") #ifndef _Token_h #define _Token_h 1 #pragma once #define CHAR_BITS 8 #define LONG_BITS 32 #if (4 * CHAR_BITS) <= LONG_BITS typedef unsigned long Token; #define token(a,b,c,d) ((((a)<<CHAR_BITS | (b))<<CHAR_BITS | (c))<<CHAR_BITS | (d)) #endif #endif _Token_h ================================== eg: const Token HELP = token('H','E','L','P'); Token t; ... switch(t) { case HELP: ... } ================================= -- Bill Triggs Oxford University Computing Laboratory 11 Keble Rd Oxford OX1 3QD, U.K.
peterson@choctaw.csc.ti.com (Bob Peterson) (03/05/91)
In article <27CD159D.6581@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to peterson@choctaw.csc.ti.com (Bob Peterson): >>In article <27C95D3A.1715@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>>Presumably, a well-designed class hierarchy will use a virtual >>>function to store objects ... >> >>Researchers and vendors have taken a number of implementation approaches >>... The details of the various implementations vary widely, but few depend >>on virtual functions to supply type information. >Is there any reason to avoid virtual functions for object storage? >I had not envisioned a virtual function to return type information, >but rather a virtual function to actually _do_the_work_, so no one >outside of that function need know the actual type of the object >stored. Several solutions exist to the problem of persistence. Basing a solution on virtual functions is only one approach. I failed to clearly make my point: virtual functions are not the only foundation on which to build persistence. I know you don't wish to limit the mechanisms available to implementors of persistence, but the current language definition does contain hoops through which persistent implementations must jump. Certain requirements that must satisfied by some persistent object systems, e.g., type evolution, are more significantly more difficult to satisfy using the virtual function approach. (Type evolution, i.e., changing the definition of a class after instances and modifying the already saved instances as appropriate, may be substantially easier if translation between computational and stored formats is done by functions operating on the class, rather than functions of the class.) That is, if an instance saved using an out of date definition is read, either the read must fail or the instance must be translated into the current definition. I believe this implies something outside the class must understand how the class changed, which a member function may have difficulty doing. A number of C++ OODB efforts implement translation using mechanisms other than member functions. Some do not implement translation at all. Of the persistent C++ implementations using the virtual function approach that I know about, all seem to be "one of" systems, by which I mean they do not seem intended as general solutions to persistence for C++. Indeed, some developers of such solutions now regret taking that approach, arguing that a more general approach should be used. Many took the "virtual function does the work" approach because, at the time they did their initial design, nothing else was available. Solutions to the persistence requirement that don't implement translation using member functions need some way of identifying the type of the object being translated. Over the last few years my group explored several approaches, including a preprocessor that generated member functions returning type information with a version specifier, and a preprocessor that enhanced "new" with a call to update a type table. We've also reviewed the member function-based implementations of Interviews, and similar systems. I see the need for a standard mechanism for accessing a class instance's type. I believe a general mechanism, similar in spirit to the property lists found in Lisp, would support not only type information, but the needs of other enhanced facilities such as programming environments. I'd like to see the compiler insert into each class a static member function that returns a pointer to a singly linked list of pointers to a class instance. The class pointed to would have defined only a virtual function returning a descriptor. The descriptor would describe the sort of information the actual instance contained, e.g., a description of a type. Users would be able to derive from the language-specified class description expanded classes to support * type information at various levels of detail, e.g., a compiled form for translation purposes and a source form for browsers; * "wrapper" methods (like Lisp's before and after methods); * evolution methods, which accept an instance created using a previous definition of a class and return an instance usable under the current definition; * location information if the actual instance is not located in the current address space, to enable transparent forwarding in a distributed environment; * references to source code, e.g., file name or memory address, for use by a programming tool such as Saber C++ and Objectworks for C++; * etc., etc., etc. There would be some cost, probably on the order of one null pointer per class, if the facility isn't used by a particular application. >Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> >"It's not a security hole, it's a SECURITY ABYSS." -- Christoph Splittgerber > (with reference to the upage bug in Interactive UNIX and Everex ESIX) Bob Peterson Compuserve: 70235,326 Expressway Site, Texas Instruments Internet: peterson@csc.ti.com North Building, P.O. Box 655474, MS238 Landline: (214) 995-6080 2nd Floor, Dallas, Texas, USA 75265 CSC Aisle C3 Bob Peterson Compuserve: 70235,326 Expressway Site, Texas Instruments Internet: peterson@csc.ti.com North Building, P.O. Box 655474, MS238 Landline: (214) 995-6080 2nd Floor, Dallas, Texas, USA 75265 CSC Aisle C3
chip@tct.uucp (Chip Salzenberg) (03/06/91)
According to bill@robots.oxford.ac.uk (Bill Triggs): >Type info is *essential* for safe down-casting. The alternative >of pushing all Derived functions (and implicitly members) into >the Base -- either as full implementations or as empty or >error-calling stubs -- is simply laughable... "Laughable" is not a very helpful description. Specific objections, please. Note that downcasting to Derived* can itself be _one_ virtual function of Base. >Creation of an instance of an implicitly specified class.... >It seems that an appropriately global `class-designator' is >indispensable for this, whether string or global address or >global enumeration constant ... The class name is already guaranteed to be unique (name equivalence and all that). What need of more? >... appropriate `static Class* Class::' or `friend Class*' >new_object() or read_object(), parse_object() functions... What's missing from a virtual function "Base *Base::clone() const"? >... Switch()-ing on type ought to be possible. I fail to see why such a feature should be supported, when the difficulties of producing a global (multi-module) class->integer equivalence are so well known. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "All this is conjecture of course, since I *only* post in the nude. Nothing comes between me and my t.b. Nothing." -- Bill Coderre
jimad@microsoft.UUCP (Jim ADCOCK) (03/07/91)
In article <1991Mar5.143615.5847@csc.ti.com> peterson@choctaw.csc.ti.com (Bob Peterson) writes: | I see the need for a standard mechanism for accessing a class |instance's type. I believe a general mechanism, similar in spirit to |the property lists found in Lisp, would support not only type |information, but the needs of other enhanced facilities such as |programming environments. I'd like to see the compiler insert into |each class a static member function that returns a pointer to a singly |linked list of pointers to a class instance. The class pointed to |would have defined only a virtual function returning a descriptor. The |descriptor would describe the sort of information the actual instance |contained, e.g., a description of a type. Users would be able to |derive from the language-specified class description expanded classes .... Seems we're getting hung up arguing details of compiler implementation rather than issues of language definition. How should run-time type information be represented in the language -- as opposed to in the resulting executable?
chip@tct.uucp (Chip Salzenberg) (03/07/91)
According to peterson@choctaw.csc.ti.com (Bob Peterson): >Virtual functions are not the only foundation on which to build >persistence. Quite. "There's more than one way to do it." [tm] Larry Wall. >That is, if an instance saved using an out of date definition is read, >either the read must fail or the instance must be translated into the >current definition. I believe this implies something outside the class >must understand how the class changed, which a member function may have >difficulty doing. I am not persuaded that this conclusion is necessary. _Something_ must know about the changes; why not the class itself? In other words, what is the difficulty with giving to each class the responsibility for remembering its own revision history? I'd like to hear specific technical problems. >Solutions to the persistence requirement that don't implement >translation using member functions need some way of identifying the >type of the object being translated. What is wrong with the simple "ClassName:version"? >I see the need for a standard mechanism for accessing a class >instance's type. I fail to see any benefit such a feature would provide for persistence, since other information essential for freezing an object (private member variables, for instance) will be unavailable from the outside. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "All this is conjecture of course, since I *only* post in the nude. Nothing comes between me and my t.b. Nothing." -- Bill Coderre
beard@ux5.lbl.gov (Patrick C Beard) (03/07/91)
The information about what class an object is can be easily obtained by using the pointer to the object's virtual function table as the class id, which whill always be unique for every class. This could be quite easy to add to the language and easy to implement. Class relationships would be easy to implement by putting a link field in the vtable which points to a class's super-class. I agree that using run-time type information is quite easy to abuse, but then so is multiple-inheritance. These are powerful tools, but we shouldn't be forced to live without them. -- // Patrick C. Beard, Software Engineer, Berkeley Systems, Inc. // "Heroes of technology." // beard@lbl.gov, d0346@applelink.apple.com (ATTN: Patrick)
dsouza@gwen.cad.mcc.com (Desmond Dsouza) (03/08/91)
In article <10721@dog.ee.lbl.gov> beard@ux5.lbl.gov (Patrick C Beard) writes: > The information about what class an object is can be easily obtained by using > the pointer to the object's virtual function table as the class id, which > whill always be unique for every class. This could be quite easy to add > to the language and easy to implement. Class relationships would be easy > to implement by putting a link field in the vtable which points to a > class's super-class. Unfortunately, most current compilers do not guarantee a single vtable for each class, given separate compilation in a traditional compile-link-load environment. Desmond. -- ------------------------------------------------------------------------------- Desmond D'Souza, MCC CAD Program | ARPA: dsouza@mcc.com | Phone: [512] 338-3324 Box 200195, Austin, TX 78720 | UUCP: {uunet,harvard,gatech,pyramid}!cs.utexas.edu!milano!cadillac!dsouza
peterson@choctaw.csc.ti.com (Bob Peterson) (03/08/91)
I should point out that for purposes of this discussion I assume an OODB in which changing a class instance to conform to a revised definition is done "as needed," rather than "all at once," or batch. In the latter environment a utility would carry out the changes, all programs impacted are immediately recompiled, and the issue of runtime type information becomes moot. Also, almost the same set of issues arise when considering movement of a class instance into or out of an address space, including movement for persistence and for interprocess communication. In article <27D56A26.2881@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to peterson@choctaw.csc.ti.com (Bob Peterson): >>That is, if an instance saved using an out of date definition is read, >>either the read must fail or the instance must be translated into the >>current definition. I believe this implies something outside the class >>must understand how the class changed, which a member function may have >>difficulty doing. > >I am not persuaded that this conclusion is necessary. _Something_ >must know about the changes; why not the class itself? In other >words, what is the difficulty with giving to each class the >responsibility for remembering its own revision history? I'd like to >hear specific technical problems. Technical problems arise in several areas. The first two are hard issues, while the second two seem easier to solve. * Certain properties of an object's representation depend on the specific runtime environment. Examples include the implementation details of virtual functions, the implementation details virtual inheritance, how ordering of the names of inherited classes impacts the layout of local and inherited member variables, how the compiler chooses to order the noncontiguous parts of the class' state, and where and how much padding is inserted to obtain correct alignment. Translating from external format to internal format must take all of these factors into account. Any of the above can change without changing the meaning of a program, and, quite correctly, the language definition excludes specification of any of them. Do we wish to require the class implementation to understand all possible implementations of the above properties, past as well as present? I believe such details, which are not part of the language, should not be reflected in the class. They must be taken care of outside the class, i.e., by an external translation mechanism. (I do assume we expect the class itself to be portable, as well as instances of the class.) * Certain kinds of type evolution occur outside the immediate class. Examples of such changes include changing an ancestor class's inheritance list, and changing from ordinary to virtual inheritance. While such changes can alter the immediate class's state, forcing recognition of the change in the immediate class may be undesirable. For example, a class should not care about insertion of an additional privately inherited class in an ancestor. Forcing such recognition could easily result in substantial maintenance, an effort not required if a different approach is used. * If I need to add a class library to my development environment I must somehow add the appropriate translation methods to the library. This implies I must have source code. If instead of using methods, the system derives translation data from header files, I _might_ be able to use a binary distribution of the library. Requiring source code changes precludes using a library available to me only as a binary. * How many prior versions must a class know about? In the real world, the number of revisions will certainly be larger than one, and could easily exceed 20 over the lifetime of a database. As revisions accumulate the evolution function(s) embedded in the class will become large. (This isn't really a technical problem, but a management problem, I confess, but I believe it is relevant.) For a good discussion of over 20 ways to change a class, and the meaning of each change, see "Semantics and Implementation of Schema Evolution in Object-Oriented Databases," Banerjee & Kim, Proceedings of SIGMOD 1987, pp. 311-322. While the paper discusses a Lisp-based OODB, it is quite relevant to C++ class evolution. About one quarter of the changes in their list do not show up in the immediate class. >>I see the need for a standard mechanism for accessing a class >>instance's type. > >I fail to see any benefit such a feature would provide for >persistence, since other information essential for freezing an object >(private member variables, for instance) will be unavailable from the >outside. Why do you assume the absence of a data dictionary? In fact, most existing OODB implementations use a data dictionary, i.e., a complete description of all data stored in the database. Frequently a utility program initially loads the dictionary from information in header files. That same utility should be able to detect changes in a class definition, no matter what the origin of the change. More advanced dictionaries support browsing of the definitions. Future dictionaries will feature interactive interfaces. Developers will make class changes through the dictionary, with interactive support for visualizing the impact of the changes, as well as dictionary support for ensuring the changes reflect correct semantics and are supportable by the translation mechanism. Given a data dictionary, when an application runs and fetches an object saved using a now out of date definition the OODB runtime can consult the dictionary to figure out what changes must be made in order to correctly utilize the object under the definition known to the application. The OODB runtime may alter the translation of the object's state, providing default values for added data members, connecting as appropriate to the language's virtual function mechanism, etc. >-- >Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> > "All this is conjecture of course, since I *only* post in the nude. > Nothing comes between me and my t.b. Nothing." -- Bill Coderre Bob Bob Peterson Compuserve: 70235,326 Expressway Site, Texas Instruments Internet: peterson@csc.ti.com North Building, P.O. Box 655474, MS238 Landline: (214) 995-6080 2nd Floor, Dallas, Texas, USA 75265 CSC Aisle C3
jbuck@galileo.berkeley.edu (Joe Buck) (03/09/91)
In article <10721@dog.ee.lbl.gov>, beard@ux5.lbl.gov (Patrick C Beard) writes: |> The information about what class an object is can be easily obtained by using |> the pointer to the object's virtual function table as the class id, which |> whill always be unique for every class. This could be quite easy to add |> to the language and easy to implement. Class relationships would be easy |> to implement by putting a link field in the vtable which points to a |> class's super-class. False. Several reasons: 1). The language standard says nothing about virtual function tables. It only specifies the effect of calling virtual functions. A different data structure could be used, and in some cases might be more efficient. 2). You can't count on testing two objects for the same type by comparing their vtbl pointers anyway. The compiler may have had to duplicate the vtbl; for example, because of incremental linking or because it didn't have enough information to avoid generating the table more than once. -- Joe Buck jbuck@galileo.berkeley.edu {uunet,ucbvax}!galileo.berkeley.edu!jbuck
chip@tct.uucp (Chip Salzenberg) (03/09/91)
According to beard@ux5 (Patrick C Beard): >The information about what class an object is can be easily obtained by using >the pointer to the object's virtual function table as the class id, which >will always be unique for every class. Current implementations of C++ may have more than one copy of the virtual function table for any given class. Sorry. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "Most of my code is written by myself. That is why so little gets done." -- Herman "HLLs will never fly" Rubin
rfg@NCD.COM (Ron Guilmette) (03/10/91)
Regarding using numeric codes for run-time type identifiers: In article <27D270A7.2E3B@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: > >Why not use the class name itself? One reason is that `class C' or `struct S' can have two or more meanings. Each of several compilations units may define its own `class C'. >>A much cleaner solution would have been to enlist the compiler & linker >>to assign the globally unique "type codes" up front, prior to run-time. >>This would have allowed us to avoid the (very expensive) table lookups >>which we did within the kernel for each transmitted message. > >A string lookup though a set of strings fixed at compile time needs to >be rewritten if it is "very expensive." You don't understand. If somebody charges you only $2.50 per ton of air, it is still "too expensive". Anything is "expensive" if you don't really need it. -- // Ron Guilmette - C++ Entomologist // Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg // New motto: If it ain't broke, try using a bigger hammer.
rfg@NCD.COM (Ron Guilmette) (03/10/91)
In article <11792@pasteur.Berkeley.EDU> jbuck@galileo.berkeley.edu (Joe Buck) writes: +In article <10721@dog.ee.lbl.gov>, beard@ux5.lbl.gov (Patrick C Beard) writes: +|> The information about what class an object is can be easily obtained by using +|> the pointer to the object's virtual function table as the class id, which +|> whill always be unique for every class... + +False. Several reasons: ... +2). You can't count on testing two objects for the same type by comparing +their vtbl pointers anyway. The compiler may have had to duplicate the +vtbl; for example, because of incremental linking or because it didn't +have enough information to avoid generating the table more than once. And this happens quite often! Addresses of vtables CANNOT BE USED as globally unique class identifiers. Period. -- // Ron Guilmette - C++ Entomologist // Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg // New motto: If it ain't broke, try using a bigger hammer.
rfg@NCD.COM (Ron Guilmette) (03/10/91)
In article <27D3E544.619A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes:
+According to bill@robots.oxford.ac.uk (Bill Triggs):
+>Creation of an instance of an implicitly specified class....
+>It seems that an appropriately global `class-designator' is
+>indispensable for this, whether string or global address or
+>global enumeration constant ...
+
+The class name is already guaranteed to be unique (name equivalence
+and all that). What need of more?
Not only are file-scope class names not guaranteed to be unique across
multiple independent compilation units (which may later be combined to
form a single program) but class names need not even be unique *within*
a single compilation unit. (Remember that any given compilation unit may
contain numerous independent declarative scopes.)
--
// Ron Guilmette - C++ Entomologist
// Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg
// New motto: If it ain't broke, try using a bigger hammer.
peterson@choctaw.csc.ti.com (Bob Peterson) (03/11/91)
In article <71091@microsoft.UUCP> jimad@microsoft.UUCP (Jim ADCOCK) writes: >In article <1991Mar5.143615.5847@csc.ti.com> peterson@choctaw.csc.ti.com (Bob Peterson) writes: >| I see the need for a standard mechanism for accessing a class >|instance's type. I believe a general mechanism, similar in spirit to >|the property lists found in Lisp, would support not only type >|information, but the needs of other enhanced facilities such as >|programming environments. I'd like to see the compiler insert into >|each class a static member function that returns a pointer to a singly >|linked list of pointers to a class instance. The class pointed to >|would have defined only a virtual function returning a descriptor. The >|descriptor would describe the sort of information the actual instance >|contained, e.g., a description of a type. Users would be able to >|derive from the language-specified class description expanded classes > >.... > >Seems we're getting hung up arguing details of compiler implementation >rather than issues of language definition. How should run-time >type information be represented in the language -- as opposed to in the >resulting executable? Sorry, I misspoke. My phrasing above, using the word "compiler," mislead you. I intended to suggest the _language_ be enhanced to require a conforming language processor to predefine in each class a static member function, which returns the list described above. Also added would be a predefined interface through which a user program could add application-specific elements onto the list. The language would predefine some descriptor values, reserve additional values for future enhancements, and reserve a range of values for user-defined (and nonstandard) functionality. Similarly, a conforming language processor would be required to implement certain descriptors, and would be generate others based on user-specified options, i.e., when the user asks for them. The spirit of my suggestion was along the lines John Interrante and Mark Linton suggests in their "Runtime Access to Type Information in C++" paper (in the Proceedings of the 1990 USENIX C++ Conference, pp. 233-240). The paper describes "Dossier," and experimental experience using the idea in Interviews' Unidraw library. However, I would like to extend the idea to cover a variety of future needs, not just the single idea of runtime type information. In effect, I want an extensible mechanism able to supply a variety of metaclass information. I also suggest defining a static function, rather than a member variable, for additional flexibility, and to avoid the "sometimes it is virtual, other times not" suggestion made in the above paper. Bob Bob Peterson Compuserve: 70235,326 Expressway Site, Texas Instruments Internet: peterson@csc.ti.com North Building, P.O. Box 655474, MS238 Landline: (214) 995-6080 2nd Floor, Dallas, Texas, USA 75265 CSC Aisle C3
chip@tct.uucp (Chip Salzenberg) (03/12/91)
According to peterson@choctaw.csc.ti.com (Bob Peterson): >In article <27D56A26.2881@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>What is the difficulty with giving to each class the responsibility for >>remembering its own revision history? I'd like to hear specific technical >>problems. > > * Certain properties of an object's representation depend on the > specific runtime environment. Examples include > the implementation details of virtual functions, > the implementation details virtual inheritance, > ... > Translating from external format to internal format must take all of > these factors into account. But these issues shouldn't enter into the discussion at all! Any implementation of persistent objects for C++ will presumably be designed to require no more of its environment than the presence of a C++ language compiler/interpreter. In particular, it will not need to know any details of the C++ implementation. For the persistent-object support to be aware of the details of a particular C++ implementation is, in my opinion, a design error. In any case, the C++ implementation details are not relevant to the question of how a virtual member function is at a disadvantage compared to a non-member function in freezing and restoring objects, which is what I asked about. > * Certain kinds of type evolution occur outside the immediate class. >Examples of such changes include changing an ancestor class's >inheritance list, and changing from ordinary to virtual inheritance. Such type evolution must be dealt with by any store/retrieve function, whether it be a virtual member function or some other kind. Realize that, as I envision it, the virtual functions for storage and retrieval will be generated automatically from the data dictionary. That same data dictionary will also be used to generate the C++ class definition. Internal and external type evolution are handled equally well by such a system. > * If I need to add a class library to my development environment I >must somehow add the appropriate translation methods to the library. >This implies I must have source code. Yes -- iff you desire to create persistent objects of library-defined types. I would avoid this need by (1) getting source code, which IMHO is vital for C++ programming anyway, or (2) creating persistent classes that contain or derive from the library-defined classes. > * How many prior versions must a class know about? ... As revisions > accumulate the evolution function(s) embedded in the class will become > large. True but irrelevant to the virtual-vs.-friend issue. >> ... other information essential for freezing an object >>(private member variables, for instance) will be unavailable >>from the outside. > > Why do you assume the absence of a data dictionary? In fact, I did assume a data dictionary for my approach described earlier. I just didn't think of it when I wrote the above sentence. >The OODB runtime may alter the translation of the object's state, >providing default values for added data members, connecting as >appropriate to the language's virtual function mechanism, etc. To suggest that information and procedures of this kind belongs anywhere _except_ the class definition is, IMO, preposterous. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "Most of my code is written by myself. That is why so little gets done." -- Herman "HLLs will never fly" Rubin
cok@islsun.Kodak.COM (David Cok) (03/12/91)
In article <4338@lupine.NCD.COM> rfg@NCD.COM (Ron Guilmette) writes: >In article <27D3E544.619A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >+According to bill@robots.oxford.ac.uk (Bill Triggs): >+>Creation of an instance of an implicitly specified class.... >+>It seems that an appropriately global `class-designator' is >+>indispensable for this, whether string or global address or >+>global enumeration constant ... >+ >+The class name is already guaranteed to be unique (name equivalence >+and all that). What need of more? > >Not only are file-scope class names not guaranteed to be unique across >multiple independent compilation units (which may later be combined to >form a single program) but class names need not even be unique *within* >a single compilation unit. (Remember that any given compilation unit may >contain numerous independent declarative scopes.) >-- I agree that class names can be reused in local scopes within functions and other classes. But can they be reused at file scope in different compilation units? I thought these were effectively global, as free functions are. If you try this with Sun C++ (and the classes happen to have non-inline functions with the same name and parameter types) you will get a link error with a multiply defined function error. Not to necessarily advocate the use of a class name as a type tag, but it seems that it would work, with suitable mangling for local scopes, if one wanted to do it. David R. Cok Eastman Kodak Co. cok@Kodak.COM
chip@tct.uucp (Chip Salzenberg) (03/13/91)
According to rfg@NCD.COM (Ron Guilmette): >In article <27D3E544.619A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >+The class name is already guaranteed to be unique (name equivalence >+and all that). What need of more? > >Not only are file-scope class names not guaranteed to be unique across >multiple independent compilation units (which may later be combined to >form a single program) but class names need not even be unique *within* >a single compilation unit. Quite right; I had forgotton about local types. On the other hand, I do not believe that avoidance of local types for persistent and/or transmitted data is too much to ask of a programmer, especially since such a restriction would avoid the need for two class names (lexical and unique). -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "Most of my code is written by myself. That is why so little gets done." -- Herman "HLLs will never fly" Rubin
rfg@NCD.COM (Ron Guilmette) (03/25/91)
In article <1991Mar12.123811.13701@kodak.kodak.com> cok@islsun.Kodak.COM (David Cok) writes: +In article <4338@lupine.NCD.COM> rfg@NCD.COM (Ron Guilmette) writes: +>In article <27D3E544.619A@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: +>+According to bill@robots.oxford.ac.uk (Bill Triggs): +>+>Creation of an instance of an implicitly specified class.... +>+>It seems that an appropriately global `class-designator' is +>+>indispensable for this, whether string or global address or +>+>global enumeration constant ... +>+ +>+The class name is already guaranteed to be unique (name equivalence +>+and all that). What need of more? +> +>Not only are file-scope class names not guaranteed to be unique across +>multiple independent compilation units (which may later be combined to +>form a single program) but class names need not even be unique *within* +>a single compilation unit. (Remember that any given compilation unit may +>contain numerous independent declarative scopes.) +>-- + +I agree that class names can be reused in local scopes within functions and +other classes. But can they be reused at file scope in different +compilation units? Why ask me? Why not try it yourself? You do have a C++ compiler (or translator) don't you? +I thought these were effectively global, as free +functions are. What is a "free function"? I never heard of such a thing before. Most function I have seen are quite expensive. :-) +If you try this with Sun C++ (and the classes happen to +have non-inline functions with the same name and parameter types) you +will get a link error with a multiply defined function error. Yes. On every other Thursday, if there is a full-moon and if a dog is howling, you will get a link-time error. In general however, you will not. +Not to +necessarily advocate the use of a class name as a type tag, but it seems +that it would work, with suitable mangling for local scopes, if one +wanted to do it. Well you could try to use tags a "globally" unique identifiers (across compilation units) for their respective classes, but that would tend to be unsafe. I think that there are safer (and more efficient) ways to provide globally unique "type identifiers" for class types. -- // Ron ("Shoot From The Hip") Guilmette // Internet: rfg@ncd.com uucp: ...uunet!lupine!rfg // New motto: If it ain't broke, try using a bigger hammer.
chip@tct.uucp (Chip Salzenberg) (03/28/91)
According to rfg@NCD.COM (Ron Guilmette): >According to cok@islsun.Kodak.COM (David Cok): >+I agree that class names can be reused in local scopes within functions and >+other classes. But can they be reused at file scope in different >+compilation units? > >Why ask me? Why not try it yourself? You do have a C++ compiler (or >translator) don't you? We have progressed beyond the point of "My compiler allows X, therefore it's legal", haven't we? The ARM explicitly notes its dependence on name equivalence. Flouting that rule is just asking for trouble. -- Chip Salzenberg at Teltronics/TCT <chip@tct.uucp>, <uunet!pdn!tct!chip> "All this is conjecture of course, since I *only* post in the nude. Nothing comes between me and my t.b. Nothing." -- Bill Coderre