chip@tct.uucp (Chip Salzenberg) (03/29/91)
According to jjb@hardy.acs.washington.edu (Jim Black): >In article <27EF838D.4115@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>Anything a type tag can do, a virtual function can do ... > >Try to snapshot an object, and bring it back to life in a different address >space. The virtual function table pointer is meaningless at that point, >because it points into the wrong address space. You want some way of >knowing the type of the object - SPECIFICALLY TO RESTORE THE VIRTUAL >FUNCTION TABLE POINTER! I think we all agree that object freezing (translation of an object to dead bytes) is essentially a non-problem, or at least a much simpler problem than the issue Jim raises, namely, object thawing (translation of dead bytes to an object). So I will concentrate on the latter. Rhetorical question: If an object is just dead bytes frozen in a file or shared memory segment or whatever, how does the compiler know any better than the programmer what the real type of the frozen object? Answer: It hasn't a clue. The language type system is inoperative on dead arrays of bytes. Neither typeof() nor any other type-specific mechanism can do your work for you here. You're on your own. I continue, therefore, to maintain that a compiler-supported typeof() function would be of little utility, and none for object thawing. >I want to just snapshot the whole "new"'d range of memory and be able >to reinstantiate it at a later time ... What if the members are pointers or contain pointers? You can't seriously expect all persistent objects to be pointer-free! (Talk about not understanding the situation...) >>If you want or need to change an argument in an overriding virtual >>function, then it is obvious that you're not simply providing a new >>implementation of the _same_ function. ... blame your design. > >class B { > operator <= (B&); >}; >class D : B { > operator <= (D&); >}; > >I dunno, call this bad design, but I don't think so. Then we must disagree, because to me, this _is_ bad design! You, as a human, may consider "<=" to be "the same function" in both cases; but the compiler cannot make that leap. To the compiler, the first function implements "less than or equal to a B", and the second function implements "less than or equal to a D" -- quite different! Thus they are _not_ two implementations of the _same_ function, and they should not be given the same name. -- 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
kelley@mpd.tandem.com (Michael Kelley) (03/30/91)
>According to jjb@hardy.acs.washington.edu (Jim Black): >>In article <27EF838D.4115@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >>>Anything a type tag can do, a virtual function can do ... >> >>Try to snapshot an object, and bring it back to life in a different address >>space. The virtual function table pointer is meaningless at that point, >>because it points into the wrong address space. You want some way of >>knowing the type of the object - SPECIFICALLY TO RESTORE THE VIRTUAL >>FUNCTION TABLE POINTER! > >I think we all agree that object freezing (translation of an object to >dead bytes) is essentially a non-problem, or at least a much simpler >problem than the issue Jim raises, namely, object thawing (translation >of dead bytes to an object). So I will concentrate on the latter. Wait a minute; don't you say (five paragraphs down) that pointers within objects present a real problem? Wouldn't you agree it's in what you call freezing, as well as thawing? (Aside: nifty new terms, Chip.) How is it that you then infer the original poster doesn't understand the situation? Maybe he was posting *in order to understand*, but I'd imagine it doesn't help him when you contradict yourself... > >Rhetorical question: If an object is just dead bytes frozen in a file >or shared memory segment or whatever, how does the compiler know any >better than the programmer what the real type of the frozen object? > >Answer: It hasn't a clue. The language type system is inoperative on >dead arrays of bytes. Neither typeof() nor any other type-specific >mechanism can do your work for you here. You're on your own. > >I continue, therefore, to maintain that a compiler-supported typeof() >function would be of little utility, and none for object thawing. Whoa! It's great that you understand the difference between support for functionality at the language level as opposed to support from a library. Obviously, ADA went too far in trying to do too much as a language. But is it realistic to completely decouple the language features from supporting libraries? Not here, I think. (Just why do you think there is an ANSI-C++ library committee that works *with* the people responsible for the language?) I'll go out on a limb and argue that in order to retrieve/activate/thaw an object, there MUST be a typeof() defined within the language in order to store/pacify/freeze *any* object and it's type. Further, this typeof() must be able to determine equivalence between tags based on class hierarchy, unless you have either (1) dynamic loading or (2) no need for anything but exact type matching between a stored object and the object you use in memory. If you have neither, then yes, you got me there, the semantics of your functions must be very well defined. I think I'm with you in that reading an object back in is still a big hairy mess. I think at a minimum, we'd need a special constructor (defined as part of the language), which just sets up virtual tables, and then calls a conversion routine to map "neutralized" data into some architecture-specific format. As an aside, I don't believe it's possible to make conversion a part of the language (witness pointers and the like). But surely we could agree on conversion routine names a little-better-than-brain-dead linker would know about so that a *standard* library could [de]neutralize *any* object, independent of it's subclasses? In case you're wondering, we use a filter to sweep through object modules and libraries to look for magic cookies generated by a smart cpp, which it uses to build a data structure which encodes and provides access to our type hierarchy. No macros, no nothing, just as long as our superclass is an ancestor, it works great. Given a complete hierarchy description, a deep copy is possible with no programmer intervention... I don't think persistance is a fad that's going to go away -- I think the amount of traffic on the matter backs it up. The time is coming, I think, (those OODBMS people must be chomping at the bit!), when someone will come up with a reasonably-priced persistance package. Then, enough people will use it, and whether a program's state information is determined through persistance or on the fly will be a non-issue -- except that if your program can't do so, or your external format is incompatible with theirs, you may as well just go home. And the other guy's/gal's code will look so much like C++, you won't be able to defend yourself. Bjarne must be both extremely pleased to see so much hoopla over his baby, as well as frightened over all the "evil" plans we all have for it. But frankly, knowing that C++ was expressly designed, in part, to be compatible with ANSI C indicates to me that other such pragmatic goals may in fact be more than acceptable to the "powers that be". > >>I want to just snapshot the whole "new"'d range of memory and be able >>to reinstantiate it at a later time ... > >What if the members are pointers or contain pointers? You can't >seriously expect all persistent objects to be pointer-free! (Talk >about not understanding the situation...) > >>>If you want or need to change an argument in an overriding virtual >>>function, then it is obvious that you're not simply providing a new >>>implementation of the _same_ function. ... blame your design. >> >>class B { >> operator <= (B&); >>}; >>class D : B { >> operator <= (D&); >>}; >> >>I dunno, call this bad design, but I don't think so. > >Then we must disagree, because to me, this _is_ bad design! > >You, as a human, may consider "<=" to be "the same function" in both >cases; but the compiler cannot make that leap. To the compiler, the >first function implements "less than or equal to a B", and the second >function implements "less than or equal to a D" -- quite different! Ah, ok. "..._is_ a bad design! ... You, as a human...". Thanks for sage words from the human compiler, Chip. Now as I remember, the required "feature" is called dispatching, which was rejected *not* because it's not useful, or indicates a bad design, but because it's too doggone hard to specify in a language, much less to implement in a (fast) compiler which produces (fast and reasonably sized) code. Mike Kelley Tandem Computers, Austin, TX kelley@mpd.tandem.com (512) 244-8830 / Fax (512) 244-8247
chip@tct.com (Chip Salzenberg) (04/02/91)
According to kelley@mpd.tandem.com (Michael Kelley): >According to chip@tct.com (Chip Salzenberg): >>I think we all agree that object freezing (translation of an object to >>dead bytes) is essentially a non-problem, or at least a much simpler >>problem than the issue Jim raises, namely, object thawing (translation >>of dead bytes to an object). So I will concentrate on the latter. > >Wait a minute; don't you say (five paragraphs down) that pointers within >objects present a real problem? No, I did not say that. I did say that pointers render impractical the naive approach of "write(fd, p, sizeof(*p))". But since that naive approach is not used by anyone interested in portability, its failure does not bode ill for other freezing methods. >(Aside: nifty new terms, Chip.) I'd like to take credit for them, but I'm sure they've been used before... anyone care to prove me unoriginal? [:-)] >>The language type system is inoperative on dead arrays of bytes. > >Is it realistic to completely decouple the language features from supporting >libraries? Not here, I think. There is no useful distinction to be made between a language feature and a required library function. Consider that ANSI C permits the compiler to recognize standard functions and treat them specially, such as by expanding them inline. After all, if the language system executes a program correctly, do we care if it uses a library routine or a compiler feature for function X? I think not. So the issue of language vs. library is irrelevant to this discussion. >I'll go out on a limb and argue that in order to retrieve/activate/thaw >an object, there MUST be a typeof() defined within the language in order >to store/pacify/freeze *any* object and its type. Sorry; typeof() is neither necessary nor sufficient. Not necessary: because a virtual freeze_on() function for each type can be written. It's a bit of a pain, yes, but it can be done. Not sufficient: because a simple_minded freeze_on() function that iterates across member variables cannot deal intelligently with pointers and references, especially for recursive data structures. >I think I'm with you in that reading an object back in is still a big >hairy mess. No argument. :-) :-( >I think at a minimum, we'd need a special constructor (defined as part >of the language), which just sets up virtual tables, and then calls a >conversion routine to map "neutralized" data into some architecture-specific >format. Nope. Again because of the possibility of recursive data structures and other tricks, thaw_from() *cannot* be written automatically, unless the structures to be frozen and thawed are greatly restricted. Given that fact, it's up to you, the programmer, to use whatever convention suits you. Perhaps a one-argument constructor that takes an "istream &" will do. >As an aside, I don't believe it's possible to make conversion a >part of the language (witness pointers and the like). But surely we >could agree on conversion routine names a little-better-than-brain-dead >linker would know about so that a *standard* library could [de]neutralize >*any* object, independent of it's subclasses? The flaw in this plan is evident in the next paragraph: >In case you're wondering, we use a filter to sweep through object modules... >No macros, no nothing, just as long as our superclass is an ancestor ... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ With a common ancestor, all things are possible. The C++ language, however, is based on a type forest, not a type tree; common ancestors are the exception, not the rule. >Now as I remember, the required "feature" is called dispatching ... My comment about "B::foo(B&)" and "D::foo(D&)" not being the same function is irrelevant to issues of dynamic dispatching. It doesn't matter _how_ you decide to execute "x.foo()". B::foo() and D:foo() implement different functions, and thus should be either non-virtual or distinctly named. -- Chip Salzenberg at Teltronics/TCT <chip@tct.com>, <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
kelley@mpd.tandem.com (Michael Kelley) (04/03/91)
In article <27F77834.16D6@tct.com>, chip@tct.com (Chip Salzenberg) writes: > According to kelley@mpd.tandem.com (Michael Kelley): > > > >Is it realistic to completely decouple the language features from supporting > >libraries? Not here, I think. > > There is no useful distinction to be made between a language feature > and a required library function. Consider that ANSI C permits the > compiler to recognize standard functions and treat them specially, > such as by expanding them inline. After all, if the language system > executes a program correctly, do we care if it uses a library routine > or a compiler feature for function X? I think not. So the issue of > language vs. library is irrelevant to this discussion. > Yes, Chip, there is a distinction. If it's in the language, it (should be/is) portable. If it's not, you can only hope it's portable. Again, bare in mind that I'm NOT arguing to put every feature known to mankind in the language. I like it that C++ is as powerful as it is, yet is still pretty lean and mean. > >I'll go out on a limb and argue that in order to retrieve/activate/thaw > >an object, there MUST be a typeof() defined within the language in order > >to store/pacify/freeze *any* object and its type. > > Sorry; typeof() is neither necessary nor sufficient. > > Not necessary: because a virtual freeze_on() function for each type > can be written. It's a bit of a pain, yes, but it can be done. True, provided *you* implement the class. What happens when you get a class library from vendor X, that doesn't have a pure virtual freeze_on(), much less any implementation for freezing/thawing? Just write those objects off as "forever temporal", rather than peristable. With persistance, distribution over a network is made vastly easier than it is now--and for us (and many others, I believe) the ability to distribute is a requirement. > > Not sufficient: because a simple_minded freeze_on() function that > iterates across member variables cannot deal intelligently with > pointers and references, especially for recursive data structures. > Rather than tightening up the language, as is done with one OODBMS I've seen, I'd like to write static encoders/decoders for my class variables, and let "someone else" who understands hierarchy call them as needed. Since the hierarchy information exists in the header files, in seems natural for the compiler to give "someone else" the information. The typeof() I refer to would be used to access this information, make appropriate encoding/decoding call(s), tag the output when encoding, and compare tags when decoding. If it's not part of the language, then it *must* be done through a virtual function, using a common base class -- not viable if we hope to use libraries written outside of our companies. I did say later that conversion can't be done in the language because of pointers, which you did not see relates to your previous point. Oh well. > >I think at a minimum, we'd need a special constructor (defined as part > >of the language), which just sets up virtual tables, and then calls a > >conversion routine to map "neutralized" data into some architecture-specific > >format. > > Nope. Again because of the possibility of recursive data structures > and other tricks, thaw_from() *cannot* be written automatically, > unless the structures to be frozen and thawed are greatly restricted. > Given that fact, it's up to you, the programmer, to use whatever > convention suits you. Perhaps a one-argument constructor that takes > an "istream &" will do. Again, I'm not arguing for generated conversion. And yes, an istream&, or an XDR*, will do just fine -- for me, for you, for everyone who's ever wrestled with persistance. But if we don't all do it the same way, we're not going to be able to leverage off of each other's work. We'll have a forest of objects that can't talk to each other. > > >As an aside, I don't believe it's possible to make conversion a > >part of the language (witness pointers and the like). But surely we > >could agree on conversion routine names a little-better-than-brain-dead > >linker would know about so that a *standard* library could [de]neutralize > >*any* object, independent of it's subclasses? > > The flaw in this plan is evident in the next paragraph: > > >In case you're wondering, we use a filter to sweep through object modules... > >No macros, no nothing, just as long as our superclass is an ancestor ... > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > > With a common ancestor, all things are possible. The C++ language, > however, is based on a type forest, not a type tree; common ancestors > are the exception, not the rule. > Exactly. The operative phrase was "just as long as". It works *only* because we have a common base class, and I don't like that it has to be that way *because* it is the exception. And I can't see any way to make it work for a forest without help from the language. As an aside, I'm well aware that there's a serious problem with class name clashes. > >Now as I remember, the required "feature" is called dispatching ... > > My comment about "B::foo(B&)" and "D::foo(D&)" not being the same > function is irrelevant to issues of dynamic dispatching. It doesn't > matter _how_ you decide to execute "x.foo()". B::foo() and D:foo() > implement different functions, and thus should be either non-virtual > or distinctly named. Huh? I was addressing the invocation of x.foo(y), in keeping with the prototypes you put down. What matters is that to invoke the right "x.foo()", I don't have to know what type "x" is; but, to invoke the "x.foo(y)" I want, *I've* got to tell the compiler what type "y" really refers to -- the compiler will make a call based on *declared* type otherwise. Maybe I have it all wrong, but I think this is exactly what dispatching is all about. Again, I realize this may be an intractable problem within C++, but to write off requests for such functionality as evidence of bad design is a serious mistake. I'm an unabashed C++ fan, but am not so rabid to reject anything that can't be done using the language as it is today. Some would say "you should use Objective-C or Smalltalk", but for my work, the advantage still goes to C++. Mike Kelley Tandem Computers, Austin, TX kelley@mpd.tandem.com (512) 244-8830 / Fax (512) 244-8247
chip@tct.com (Chip Salzenberg) (04/04/91)
According to kelley@mpd.tandem.com (Michael Kelley): >In article <27F77834.16D6@tct.com>, chip@tct.com (Chip Salzenberg) writes: >> There is no useful distinction to be made between a language feature >> and a required library function. > >Yes, Chip, there is a distinction. If it's in the language, it (should be/is) >portable. If it's not, you can only hope it's portable. You missed the word "required", as in "required library function". I stand by my quoted statement. >> Sorry; typeof() is neither necessary nor sufficient. >> >> Not necessary: because a virtual freeze_on() function for each type >> can be written. It's a bit of a pain, yes, but it can be done. > >True, provided *you* implement the class. What happens when you get a class >library from vendor X ... This kind of problem is why I expect attempts at distribution of binary-only C++ classes to have, at best, limited success. C++ requires _everything_ about a class to be in one place, namely, the class definition. In particular, all member functions must be declared there. Forbidding users to change the class definitions is too tight a straightjacket for me to impose on them. (Remember that, in current C++ implementations, adding virtual functions requires the recompilation of all constructors.) >> Not sufficient: because a simple_minded freeze_on() function that >> iterates across member variables cannot deal intelligently with >> pointers and references, especially for recursive data structures. > >Rather than tightening up the language, as is done with one OODBMS I've >seen, I'd like to write static encoders/decoders for my class variables, >and let "someone else" who understands hierarchy call them as needed. I would appreciate more detail on this proposal before making further comment. Please describe a possible implementation based on G++ or cfront. >But if we don't all do it the same way, we're not going to be able to >leverage off of each other's work. We'll have a forest of objects that >can't talk to each other. I believe that the tree forest of C++ makes type incompatibility, or the "can't talk to each other" phenomenon, inevitable. >What matters is that to invoke the right "x.foo()", I don't have to know >what type "x" is; but, to invoke the "x.foo(y)" I want, *I've* got to tell >the compiler what type "y" really refers to ... Understood. >Again, I realize this may be an intractable problem within C++, but to >write off requests for such functionality as evidence of bad design is >a serious mistake. To paraphrase The Tao Of Programming: A good design finds the simplest harmony between idea and language. In ARM C++, having two virtual functions with the same name but different parameters _is_ a bad design. In ANSI C++, that may not be true any more; but I don't have an ANSI compiler yet. Do you? :-) -- Chip Salzenberg <chip@tct.com>, <uunet!pdn!tct!chip> Brand X Industries Custodial, Refurbishing and Containment Service When You Never, Ever Want To See It Again [tm]
lerman@stpstn.UUCP (Ken Lerman) (04/05/91)
I've been sitting on the sidelines listening to the fracas, but now I think it might be useful for me to comment based on my experience with Objective-C. 1 -- It is possible to save and restore arbitrarily complex graphs of objects without explicit cooperation from each class. This is done in Objective-C. 2 -- It IS necessary (IMHO) that each object be able to identify itself. In Objective-C, this is done by having an instance variable (called isa) which points to a class structure which contains the name, the types of instance variables, ... , for that class. The construction of that structure requires compiler support. 3 -- It is NOT necessary that each class in Objective-C have the same root class, but it IS necessary that each have a first instance variable which is the "isa" pointer. 4 -- I don't understand the aversion to having a single root class for those objects which we wish to make persistent. It seems to me that multiple inheritence might be good for this. (If it is not good for this, what is it good for?) If you believe our customers, the ability to save and restore object graphs is valuable. At least for applications written in Objective-C. This is NOT intended to tell you to change your language to match mine. But if you want to add this functionality, experience with Objective-C might be valuable. Ken
chip@tct.com (Chip Salzenberg) (04/08/91)
According to lerman@stpstn.UUCP (Ken Lerman): >1 -- It is possible to save and restore arbitrarily complex graphs of >objects without explicit cooperation from each class. This is done in >Objective-C. I find this claim hard to believe. How does the freeze method know whether a |char *| member points to allocated memory? If it knows that it is allocated, how does it know whether that memory should be freed when the object is stored? Such questions seem unsolveable without cooperation from the class to be frozen. >3 -- It is NOT necessary that each class in Objective-C have the same >root class, but it IS necessary that each have a first instance >variable which is the "isa" pointer. The "isa" feature amounts to inheritance. "They have a data member in common, and they have these methods in common, but they're not derived from a common base class. Really!" Sure. >4 -- I don't understand the aversion to having a single root class for >those objects which we wish to make persistent. Nor do I. >Multiple inheritence might be good for this. I should hope so! -- Brand X Industries Custodial, Refurbishing and Containment Service: When You Never, Ever Want To See It Again [tm] Chip Salzenberg <chip@tct.com>, <uunet!pdn!tct!chip>