David Fiddes wrote:
From: Frank Heckenbach heckenb@mi.uni-erlangen.de
<snip> >Would an ID of 8 bytes matter, i.e. are there any stream files where >4 additional bytes per object would matter? <snip>
Why not make them 16 bytes? Sounds excessive maybe but that's how big a Globally Unique IDentifier (GUID)(or more pretensiously a Universally Unique IDentifier (UUID)) is...128bits....and there's a stanard for generating them...and anyone with a windows machine has an implementation of that(and the Mac and some UNIX boxes too). Upwardly comaptible with Microsofts Component Object Model(COM) and I'm sure the likes of CORBA use them too...
Is this a standard set by M$? If so, I'd only vote for using it, if it (unlike most M$ "standards") - was openly documented, - was not subject to change at M$'s decision, and - can be used for purposes like ours without possibly conflicting with other (future) uses
If this is so, I'd like to know more about these IDs.
The African Chief wrote:
BTW: Would it be possible at all? Doesn't TObject refer to TStream?
No, it doesn't. In bpcompat, TObject has a "Load" constructor that takes a TObject parameter.
But doesn't it have to refer to the stream somehow? Do you type-cast the parameter into a stream (which I wouldn't like, since IMHO type-casting should be avoided wherever possible - it's the "goto" of data structures), or did you implement some of a stream's properties within TObject (which I also don't like, see the discussion about handles)? Sorry for asking, but I don't have bpcompat at hand, and I can't imagine another way...
I don't know all the details here, but isn't it possible to have a TWindow(TObject) with Handle being a field of TWindow, and this function returning a PWindow? This would seem natural to me.
Yes, that is possible. But I don't think it does any harm to have the Handle in TObject instead.
Perhaps not (besides wasting 4 bytes...), but I only know from my own experience that I did sometimes regret thoughts like that. Therefore, I'd prefer to think about classes very exactly, especially about such base classes. And again, what's the disadvantage of putting Handle into another class?
Perhaps. But there are many times when I have wished that certain things existed in BP's TObject and TWindowsObject. Making a descendant object is fair enough, but it would have been a lot easier for me if the things were in the ancestor.
Easier perhaps, at least at first sight. But when many people use a class library and change it like that, things can get very complicated soon. It is a bit more work to create a more detailed class hierarchy and to introduce all the fields at the "right" place, but I think in the long run, it's worth the effort - and it's more in the spirit of OOP, as I understand it.
I'm probably missing something - but isn't the address of an object a "unique ID"? (One that makes it very easy to "locate" the object... ;-)
As I said, I might be missing something, but I still don't know what it is...
That assumes that you already know the object and its address.
If you used the address everywhere you use the ID now, you would know, wouldn't you?
If you already know it, there would be no point trying to locate it again.
That's what I meant by "very easy to locate"...
Also, there are a whole load of things that you can do with integers that you cannot do with addresses.
Yes, but what do you need to do with IDs? AFAICS, one only needs to compare them (for equality, not for greater/less), and this can be done with addresses.
And, suppose that, for some obscure reason, I suddenly wanted to locate all currently active object instances and/or their parents (if any), how would I do this using addresses? - but I could do this with a loop through the SelfIDs.
Where do you get the SelfIDs from? Perhaps a list of IDs stored in a parent object? You could put the addresses there instead, couldn't you?
I'm really trying to understand what you do with these IDs!
I hope, it's not only the (bad, IMHO) tendency of Windoze to use "untyped" handles for each and everything - that might not matter much in C (where everything is int anway - well almost...), but I think in Pascal we should try to keep as much typed information (e.g. typed pointers) as possible.
Pierre Phaneuf wrote:
Then it would have to be in the GPI file. Much effort. I think the few bytes we can save like that (Just those for the strings plus one pointer in each VMT) are not worth it, but perhaps somebody else wants to try himself on this problem? ;-)
This is totally optional to me, and thinking more about it, I was wondering of what use having the symbol name of an object at runtime...
I'm also wondering! I never used Delphi, so I don't know what use it has there. But from my experience, I don't know any case where this is needed - except debugging, of course, but that's another story.
I think, if some program for any reason does need this information, it could declare object constants (if implemented), or methods (in the meantime) that provide this information explicitly, but unless there's a wide need for this information (which I can't see), I don't think it should be included automatically.
(* I suggest to define a pointer to a procedure like the constructor *) (* with an *explicit* `Self' parameter, to cast `LoadPtr' to it and *) (* then call it. *)
Its the explicit part I'm trying to avoid... I guess this part will be compiler dependent, but should be among very few (with the TObject.Init constructor).
I agree. Also, I don't like the type-casting bit. But, AFAICS, making Load a virtual method (or virtual constructor in the future?) would remove all these problems. Since the VMT has been assigned at this point, a virtual method can be called without any tricks.
Also note that Java has been widely recognized as "C++ as it should have been" (by Bjarne Stroustrup among others, the designer of C++), and Java *doesn't* have multiple inheritance.
:-)
So, let's make "BP objects as they should have been"! :-)
For instance, I tried to make my class as much object-oriented as possible, free of any procedures. For example, Abstract has been made a method of TObject and a similar NoStream method has been added to prevent streaming of non-streamable objects (this is very quite not final, susceptible to removal).
Perhaps "streamability" would be a good choice for an interface (with the methods load and store)?
A thing that would be better to have than multiple inheritance would be pure virtual methods. Would allow me to remove that Abstract method! ;-)
I assume "pure virtual" is the same as "abstract" methods!? I.e., any class that has at least one abstract method can't be instantiated.
This would indeed be a much better way than Borland's "abtract method" that causes a runtime error (again, IMHO, a failure by Borland)!
The African Chief wrote:
No. C++ is clumsy and bloated. However, the problem is that MI is part of the language definition. If it were to be implemented in GPC, it wouldn't be part of any definition. It would just be a local extension that could be ignored by anybody who so wishes. I don't see that having such an extension is so bad. Some day, someone will want to use it, and it doesn't help to tell them that they don't need it.
Since interfaces are probably easier to implement (see my other mail), and sufficient for most cases (including all that I know), I suggest implementing interfaces first. If then there is a need for MI that really can't be solved with interfaces, it's time to think about adding MI (but actually I'm quite confident this won't be the case).
A purely personal thing, perhaps. If I did it my own way, without any preconceived notions, I would have an ancestor object which would contain everything that I think every object should have.
Right, everything that *every* object should have, not everything that *some* objects should have. E.g. handles: not every object has a handle, so it should not be in the base class. If in a windowing system, every instance of TWindow and its childs will have a handle, then Handle should be a field of TWindow.
I know that this complicates matters, especially in the base classes, but since this is mostly a one-time job, I think it's worth the effort.
What I think every object should have changes from time to time, and therefore, for me, it is better to start with more, than with less ;-)
As I said above, if many people with different opinions worked on such a class library, it would always be changing. AFAIK, the purpose of OOP is to have some base classes that can be used by everybody, and more special classes built on top of them that implement the needs of different applications.
I really don't see what a TWindow object and a TString object has in common that should be in TObject!
They could all have a Name, a SelfID, a Parent, and a Child.
Do you mean a name/ID/parent/child of the *class*, or of the *instance*? In the former case, I agree (except for the name, which IMHO usually isn't needed at runtime, see above), in the latter case I don't: a string doesn't have a parent or a child or a name (besides the compile-time identifier), and I don't think it usually needs an ID.
BP didn't set that trend BTW. The Smalltalk anscestor object doesn't do *ANYTHING*.
The first thing I would do to it is to change that omission ;)
Why not simply derive one (or several) classes from it with whatever you want? That would be in the spirit of OOP (not to change existing classes, but just derive new ones from them).
And what is the point of have the ancestor then - just for type compatibility ?
Yes - to do things in a typed way that need "Void" or "untyped parameters" otherwise.
So it should have only properties that really *every* object has - which, of course, can't be many, merely administrative things like a VMT pointer, perhaps an empty constructor and destructor, and properties of the class like parent class, (perhaps) child classes, (perhaps) implemented interfaces, (perhaps) ID.
Anything more (e.g. load and store methods) will not be common to all objects (e.g. a mouse object probably can't be stored in a stream), so this should be introduced in another class (or interface).
BPs constructor fills the object with zeros for instance. :-)
Not very useful at all. They might as well not have any constructor or destructor.
Agreed. Since TObject is an abstract class, it really doesn't need a constructor. The destructor is ok, since it's virtual, therefore must be declared at the base class (this allows removing any object with "Dispose(...,Done)").
Peter Gerwinski wrote:
I suggest to forget about the switch and just store a pointer to a string constant holding the name of the object in the VMT. Since we don't need to care about a 64k data segment (4GB will be enough for a while), these few extra bytes are harmless.
...if there's a real need for it. Otherwise I think, compile-time information like identfiers does not belong in the executable (apart from debug info). I think some people wouldn't like their identifiers to be revealed in a program that's distributed without source.
[...] In general, I'm against "magic" things, like the New() function, or the Write()/WriteLn() functions (in BP at least, they cannot be implemented using the language!).
So am I. That's one beauty of C: Everything, including `printf' and such, is user-definable. (Said by someone who is hacking a Pascal compiler because he cannot stand the ugly syntax of C/C++ ... ;-)
Many "un-definable" Pascal procedures could be defined with overloading and/or optional parameters. Both is possible to do in Pascal, and I hope, gpc will have them sometime.
New and Write (and so on) are (basically) a syntactical simplification for calling procedures that could have been implemented in Pascal. And this is NOT user-definable in C, it's simply missing there! Printf requires the user to explicitly state all the types, while Write does this automatically, and "p=(*t)malloc(sizeof(t))" is not quite the same as "new(p)".
Oh, BTW: "sizeof" is not user-definable in C - so they just made it an operator instead of a function, what a difference! ;-)
So don't admire C too much for this... ;-)
On Fri, 30 May 1997, Frank Heckenbach wrote:
I'm also wondering! I never used Delphi, so I don't know what use it has there. But from my experience, I don't know any case where this is needed - except debugging, of course, but that's another story.
I think, if some program for any reason does need this information, it could declare object constants (if implemented), or methods (in the meantime) that provide this information explicitly, but unless there's a wide need for this information (which I can't see), I don't think it should be included automatically.
I was thinking that it would be nice to have this in GPC only because Delphi had it, without even thinking *how* it could be useful. Now that I spent some thoughts on this, it's no longer such a good feature to have... Unnecessary bloat IMHO...
I agree. Also, I don't like the type-casting bit. But, AFAICS, making Load a virtual method (or virtual constructor in the future?) would remove all these problems. Since the VMT has been assigned at this point, a virtual method can be called without any tricks.
Yes, that's what I intend to do for the GPC-specific class library, but for the BPCOMPAT package, the Load method is a constructor, and we have to be compatible, so... :-(
Also note that Java has been widely recognized as "C++ as it should have been" (by Bjarne Stroustrup among others, the designer of C++), and Java *doesn't* have multiple inheritance.
:-)
So, let's make "BP objects as they should have been"! :-)
Yes! :-)
For instance, I tried to make my class as much object-oriented as possible, free of any procedures. For example, Abstract has been made a method of TObject and a similar NoStream method has been added to prevent streaming of non-streamable objects (this is very quite not final, susceptible to removal).
Perhaps "streamability" would be a good choice for an interface (with the methods load and store)?
Yes, for now it isn't an interface since the feature isn't there yet! :-)
A thing that would be better to have than multiple inheritance would be pure virtual methods. Would allow me to remove that Abstract method! ;-)
I assume "pure virtual" is the same as "abstract" methods!? I.e., any class that has at least one abstract method can't be instantiated.
This would indeed be a much better way than Borland's "abtract method" that causes a runtime error (again, IMHO, a failure by Borland)!
Yes, exactly! So I could remove the "Abstract" method (did you receive my CLASSES.PAS?) from TObject.
Since interfaces are probably easier to implement (see my other mail), and sufficient for most cases (including all that I know), I suggest implementing interfaces first. If then there is a need for MI that really can't be solved with interfaces, it's time to think about adding MI (but actually I'm quite confident this won't be the case).
Quite confident too. Actually, if MI gets in there and creeps into the generic class libraries that will go with GPC, I'll simply switch Modula-3.
A purely personal thing, perhaps. If I did it my own way, without any preconceived notions, I would have an ancestor object which would contain everything that I think every object should have.
Right, everything that *every* object should have, not everything that *some* objects should have. E.g. handles: not every object has a handle, so it should not be in the base class. If in a windowing system, every instance of TWindow and its childs will have a handle, then Handle should be a field of TWindow.
I know that this complicates matters, especially in the base classes, but since this is mostly a one-time job, I think it's worth the effort.
Actually, it simplify things... (IMHO)
So it should have only properties that really *every* object has - which, of course, can't be many, merely administrative things like a VMT pointer, perhaps an empty constructor and destructor, and properties of the class like parent class, (perhaps) child classes, (perhaps) implemented interfaces, (perhaps) ID.
Agreed.
Anything more (e.g. load and store methods) will not be common to all objects (e.g. a mouse object probably can't be stored in a stream), so this should be introduced in another class (or interface).
I thought about that, having a direct TObject descendent (TPersistent), but the problem lied in abstract classes that has descendents that were streameable and others that aren't. Like say TCollection, which can be a collection of streameable objects (where you want to implement TCollection itself as a streamable class) or not. Do we descend TCollection from TPersistent or TObject? If we descend from TPersistent, what do we do in case that the TCollection contains non-streamable objects?
Not very useful at all. They might as well not have any constructor or destructor.
Agreed. Since TObject is an abstract class, it really doesn't need a constructor. The destructor is ok, since it's virtual, therefore must be declared at the base class (this allows removing any object with "Dispose(...,Done)").
I like having my objects "cleaned out", but this is really personal... :-) I guess no constructor is ok if we don't do anything, but I'd like to keep this... It's easy to bypass if you don't want to use this...
...if there's a real need for it. Otherwise I think, compile-time information like identfiers does not belong in the executable (apart from debug info). I think some people wouldn't like their identifiers to be revealed in a program that's distributed without source.
Agreed.
Many "un-definable" Pascal procedures could be defined with overloading and/or optional parameters. Both is possible to do in Pascal, and I hope, gpc will have them sometime.
Well, for the moment they aren't...
Oh, BTW: "sizeof" is not user-definable in C - so they just made it an operator instead of a function, what a difference! ;-)
Didn't knew that!
Pierre Phaneuf
"The use of COBOL cripples the mind; its teaching should, therefore, be regarded as a criminal offense." - Edsger W. Dijkstra.