OOP_(re)

Wed, 4 Jun 1997 14:23:34 +0100 (BST)


On Wed, 4 Jun 1997 13:07:57 +0200 Frank Heckenbach  
wrote:
>> >We're talking about a class library for widespread use, aren't we?
>> >Imagine if everybody changed the base objects: No units of different sources
>> >would fit together!
>>
>> No, you shouldn't - IF you are going to distribute your own
>> sources for public consumption. If it is just for your own
>> private use, I don't see the problem.
>
>A problem is there if you might want to use someone else's sources based on
>the original library some time. 

True enough.

>> >The OOP way to do this is: if something doesn't suit you, derive a new class,
>> >and apply all modifications you want to the new class.
>> 
>> That is still based on the assumption that you are happy to live with 
>> the original class as it was. What if you are not? It is like having a bad 
>> ancestor with bad genes or no good genes at all. I can see the point
>> in the "pure OOP" theory, and I would do that if I was presented with a
>> ready-made ancestor (you can't choose your ancestors, or the genes
>> which you will inherit or not inherit from them). However, if I were the 
>> one to decide what that ancestor should be, I would still do it exactly 
>> like I did it.
>
>That's exactly my point. That's why I'm so picky about the base classes.
>Look at it this way: If there's a field in the base class that not everybody
>needs, there's no way for those who don't need it to remove it by deriving
>a new class, so they'll have to carry something useless in their programs.

Perhaps useless for some purposes  - but useful for others. We are all used
to data structures with "reserved" members (e.g., the TFileRec and TSearchRec
records). The fact is that although "Handle" _may_ be useless for non-windowed
objects "SelfID" is quite useful for all sorts of objects - simply because of its
function in locating object instances.

>If, however, a field that some people need is not in the base class, they can
>just derive their own base class and forget about the old base class.
>
>This is from the point of view that there are common base classes (though
>not enforced by the compiler, this will happen if many people use a library
>and write new code for it).
>
>> I may have other uses for it in TObject - and now I do. AFAICS when you
>> dispose of an object and/or call its destructor, GPC does not set it to NIL.
>
>By "it", you mean a pointer to it, do you?

Yes.

>> This has caused me a number of problems in trying to ascertain which
>> instance has been destroyed (you can't just check by finding if its value
>> is NIL). A temporary hack (until I can see a better alternative) has been
>> to set the handle to a specific negative number in the destructor (the
>> handle should normally never be negative).
>
>Doesn't work reliably. Once the object has been disposed of, its memory can
>be overwritte by other things, so in the place where the handle was there
>can be a positive number.

True - but that would not be the only check.

>Why don't you just manually set the pointer to NIL? You can use this
>generalized procedure:
>
>procedure Destroy(var p:PObject);
>begin
>  Dispose(p,Done);
>  p:=NIL
>end;

I did write a similar procedure - basically, it didn't work.
The test : "If (p<>Nil)" still returned TRUE. I am still looking
into this.

>(Better declare it as inline, though...)

Meaning? I hope you are not referring to Turbo's ghastly inline code
which has to be commented with ASM statements!

>> Big problem with pointers. Try passing a pObject or whatever
>> from one application to another, with a call to PostMessage
>> (I haven't, but I personally wouldn't try it).
>
>Hmmm... -- and what would the other application do with an ID? As far as
>you've shown so far, you use the IDs to find a pointer, and then?

And then, do whatever the message tells you to do with it.

>> They don't need to reside in any kind of shared memory. I have
>> passed messages from Win32 applications and DLLs to other
>> Win32 applications (and even Win16 applications, and a CMD.EXE
>> command prompt), all of which live in separate address spaces,
>> with a call to PostMessage.
>
>So the different apps can't address each other's objects, can they?

No.

>Then what do they do with the IDs (I really don't understand)? Do they just
>store them and send them back in some other messages? 

Yes - without using the ghastly DDE stuff.

>They could do the same with pointers (even if they can't address what the 
>pointers point to).Or what is it?

Theoretically, it is possible, but it is fraught. First of all, message parameters 
are passed as Words or Longints. You can typecast :  

	Longint ( @PMyObject )
or 
	Longint ( PMyObject )

to send the address in a message. When you get it back, it will still be in a 
type-casted Longint. How do you then do you convert this to an object 
instance; 
	p := pObject ( aLong )
	or 
	p := pObject ( @aLong )

which would you use?
then you would do;
	If p <> Nil then ...

- apart from the fact that I have no idea what all these type casts would be
doing to what I am sending and receiving (like I said, I haven't tried sending
an object's address, and I have no inclination to try it), personally, I would
prefer to send a message parameter as;

	PMyObject^.SelfID

and when I receive a message back with the SelfID in "aLong", to do;

	p := InstanceFromSelfID ( aLong )
then I would do;
	If p <> Nil then ...

>> >Yes, you're right. With Load being a constructor, and explicitly declaring
>> >the StreamRecs, this works. BTW: Does TObject need a Load constructor at all?
>>
>> Probably. I seem to remember needing it there to implement my Streams.
>
>I don't know. I just know Borland's TObject doesn't have Load. 

No. But they did a lot of things in ASM to get round some problems.

>> >I suppose you're looping through an array of IDs, right?
>>
>> No. You are dealing with data held in a list object (a TObject
>> descendant). Have you actually looked at OBJECTS.PAS?
>
>Parts of it (I don't like reading uncommented asm code very much...)-:

No - I was referring to my own OBJECTS.PAS in BPCOMPAT.

>> >What do you mean by "allocate"? Do you have a global "collection" of all
>> >"active" (=existing?) objects, with their addresses and IDs?
>>
>> An object declared in the implementation section, with a linked list
>> of existing object instances.
>
>Perhaps you need them. 

I do!

>I don't think one always needs such a "big brother",
>therefore the ID should not be part of *all* objects.

Perhaps not - but I personally think that one would need it frequently. 
Once you have it, you wonder how you ever did without it. And what 
happens when you do need it? What if you suddenly discovered the 
need to use it in connection with a collection, and a control, and a Stream 
and a file object? You would need to create a common ancestor for all of 
them, and then go back and change all your sources to derive from this 
common ancestor - or you would put it in TObject so as to avoid all that, 
and break all your code in the process.

>> You could walk the list, yes. However, not everybody is comfortable
>> with linked lists - but AFAIK, everybody can write a "for loop".
>
>(* Everybody? -- Just look at c.l.p.b. ... ;-( *)

Ok - you are right ;)  - but "for loops" are still easier for some people
than linked lists.

>I think, everybody who's serious about OOP knows linked lists, don't they?

I knew *about* linked lists for about 7 years but did not know how to
implement them. I only gained this knowledge in the past 2 years - and
I have been doing Borland-style OOP since 1989. I know some very 
experienced database programmers who have now moved to Delphi 2
(from VB, FoxPro, Access and Sage Sovereign), who write multi-user
and mission-critical database and accounting systems, who do not have
(and probably will never have) a clue about linked lists. But they can write 
"for loops".

>> >> So, I could really just do something randomly like this;
>> >>
>> >>    p := InstanceFromSelfID ( 5 );
>> >
>> >This means "give me a pointer to the object with ID 5", or what?
>>
>> Yes - when you don't even know whether such an object exists.
>> 
>> >With the changes above, you would already have the pointer instead of the
>> >ID 5, so this function call would even get superfluous.
>> 
>> You would only have the pointer by walking the list and incrementing a 
>> counter until it got to 5, and then retrieving whatever was being pointed
>> to when you get to 5. Doesn't save you any coding at all.
>
>What I meant, where do you get the "5" from? 

I said it was random. I am not saying that anyone would want to do 
something random like that - but that it could be done. And if you are
receiving this number from somewhere else (e.g., another app) then
you would need to do something like that.

>It's not hardcoded in any program, is it? AFAICS, you got this number either 
>as an ID of a specific object (of which you could just as well have got the 
>address in the first place), 

Yes, you could - and here you wouldn't need the ID.

>or by looping through all existing objects.

Looping through all objects with positive IDs - until you find the one 
you are looking for.

>Well, I see two problems addressed by you:
>
>- to check if a given ID is valid, and if so, give a pointer to the object.
>  This is almost trivial with pointers as "IDs": just check if the pointer
>  is in the list, if so return it, otherwise return NIL.

If you already have the pointer, there is no need to do any of this. So,
I think you are still missing the point. Your solution involves already
having the pointer - but what if you don't already have it? This is what
the SelfID  retrieves for you. Assuming you receive a message from
another application saying "tell the object with ID number 437 to read
some data from the serial port" - how would you proceed using your
solution?

>- to do something for all objects.
>  Just walk through the list. BTW, it's more efficient than for-looping
>  through all IDs, because you don't hit the "dead" IDs. Furthermore, it's
>  much more efficient (O(n) instead of O(n^2)) because you don't have to
>  look up every ID by searching through the list!

Like I said, destroying an object does not set the pointer thereto to NIL.
You would still come up with cases where "If p <> NIL" would return
TRUE, and yet, the object does not exist.

>(Apart from this discussion, a simple linked lists is quite an inefficient
>thing for this use (regardless whether with numeric IDs or pointers) -- as
>soon as the total number of objects gets big, searching the list gets slow
>-- other structures to be considered include trees and hash tables.)

True. But I am not sure how far this is significant. I once wrote a test program 
for ChiefAPP (an installation program) with literally hundreds of active objects
(buttons, labels, collections, gauges, dialogs, etc), with all sorts of messages
going back and forth. On a 486DX2/50, it ran like the wind. If I had 500,000 active
objects, then some slowdown might show up - but I doubt that it would be 
significant. How many instructions are these CPUs supposed to be able to handle 
per second?

However, in order not to be complacent, perhaps you could work on the OBJECTS
unit in BPCOMPAT to change the linked list therein to a more efficient structure?
Like I said, I have done my best with it. 

Best regards, The Chief 
Dr Abimbola A. Olowofoyeku (The African Chief, and the Great Elephant)
Author of:  Chief's Installer Pro v3.50 for Win16 and Win32.
Homepage:  http://ourworld.compuserve.com/homepages/African_Chief/
E-mail: laa12@cc.keele.ac.uk



The African Chief (laa12@cc.keele.ac.uk)

HTML conversion by Lluís de Yzaguirre i Maura
Institut de Lingüística Aplicada - Universitat "Pompeu Fabra"
e-mail: de_yza@upf.es