Hi Alan,
You've asked a lot of questions. Let's take them one by one:

Freeing Memory:

You didn't elaborate on what you're confused about. In Delphi, memory is
typically allocated in one of three ways: 

Allocating memory for a record:

type

  PPerson = ^TPerson;
  TPerson = record
        Name: String;
        Age: Integer;
  end; 

var
        aPerson: PPerson;
        i: Integer;
begin

        New(aPerson);
        i := sizeof(pPerson);   //  i := 4
        i := sizeof(TPerson);   //  i := 8
        i := sizeof(aPerson);   //  i := 4
        aPerson^.Name := 'Scott';
        aPerson^.Age := 20;
        Dispose(aPerson);

PPerson is a pointer to TPerson object. Being a pointer, its only takes up 4
bytes while TPerson takes up 8. aPerson, being of type PPerson also takes up
4 bytes.

New(var P: Pointer) is used to allocate a block of memory. The size of the
allocated block corresponds to the size of the type P points to. In the
example above: New(aPerson), aPerson point to a TPerson so new allocates 8
bytes of memory and stores the location (the pointer) to the beginning of
the allocated block in aPerson.

The allocated block remains allocated until its either freed by you or the
allocation ends. If your program does not end (i.e., its designed to run for
an extended period of time), and it keeps allocating memory but not freeing
it, you'll eventually consume all of the "memory" (i.e., physical + virtual)
on the machine at which point an EOutOfMemory exception will be raised. 

Call Dispose(var P: Pointer) to release the memory as in Dispose(aPerson).
Doing so will add the block of memory back to the pool of available memory.
Note that you must not use the pointer (aPerson in this case) after it has
been disposed because the block of memory it was pointing to is no longer
valid. To protect against this, you should get in the habit of:
        
        Dispose(aPerson);
        aPerson := nil; // as a note that the pointer is invalid

Creating an object:

Type
  TAnimal = class
    Name: String;
    Age: Integer;
  end;
var
  aAnimal: TAnimal;

  aAnimal := TAnimal.Create;
  aAnimal.Name := 'Fred';
  aAnimal.Age := 23;
  aAnimal.Free;

Here we're creating a instance of a class which is a form of a record with
extra intelligence and capabilities. Class instance variables (aAnimal) are
inherently pointers. Because of this, the compiler provides us a shortcut by
not requiring the ^ as in aAnimal^.Age. Prior to calling the constructor,
the compiler allocates a block of memory for the object, initializes the
memory block and then runs the constructor. Like the previous record
example, you have to release the allocated memory block when your done with
it. Because classes have destructors that must be called, you can't just do
Dispose(aAnimal). Instead you must "free" the object. This is accomplished
by:
        aAnimal.Free;

Free calls the objects destructor (or if none, the first available
destructor in the ancestor chain) and then releases the memory. As with the
record example, you should get into the habit of:

        aAnimal.Free;
        aAnimal := nil;

Delphi even provides a procedure to do this:

        FreeAndNil(aAnimal);

Allocating a block of untyped memory:

You allocate a block of untyped memory your self by, manipulate it anyway
you wish and then release it when your done.

        Ptr: PChar;

        GetMem(Ptr, 20);        // Allocates a block of 20 bytes
        Ptr[0] := '1';  // This is the same as (Ptr+0)^ := '1';
        Ptr[1] := '2';  
        (Ptr+2)^ := 3;  // This is the same as Ptr[2] := '3';
        (Ptr+3)^ := 4;
        FreeMem(Ptr);
        Ptr := nil


"Var" Parameters:

First, reread the section of Delphi help entitled: Value and variable
parameters. It does a pretty good job of explaining. 

Assume the following:

        int i;

        procedure DoubleByVal(avalue: integer)
        begin
                avalue := avalue + avalue;
        end;

        procedure DoubleByVar(var avalue: integer)
        begin
                avalue := avalue + avalue;
        end;    

begin
        i := 2;
        DoubleByVal(i); 
        // i still equals 2
        DoubleByVar(i);
        // i now equals 4

Image that "i" is a box containing a scrap of paper with the value 2 written
on it. 

When DoubleByVal is called, the compiler basically opens the "i" box, copies
the value on the piece of paper inside onto a new piece of paper, calls the
DoubleByVal passing it the new piece of paper. 

DoubleByVal basically crosses out the 2 written on the paper passed to it
can replaces it with 4. When the function exists, the compile throws the
paper away. Because the function changed the copy and not the original
value, the callers "i" remains 2.

When DoubleByVar is called, instead of making a copy of the value in "i",
the compiler passes the location of the "i" variable itself to the function.
When DoubleByVar accesses and changes "avalue" it is really accessing the
"i" variable. 

I'll leave the 3rd question to someone else as it involves a long answer.
The basic problem with a TPersistentCollection class is that it can't be
easily generic because it needs to know something about the objects stored
in it in order to persist them. The way you usually get around this is by
including the persisting function in the classes themselves which
TPersistentCollection then calls but that's not easily maintainable either. 

Delphi provides support for what you're asking for via the streaming
subsystem. It makes use of RTTI (run-time type info) which is a bookkeeping
system of sorts which allows you (and the streaming system) to determine the
number and characteristics of each published property on a class. This is
how Delphi persists forms and you can use it to persist application data as
well. 

Objects passed as parameters:

Sure, objects can be passed as parameters. Remember that objects are blocks
of allocated memory and object variables are pointers to those allocated
blocks. So when you pass an object as a parameter (by value), your passing
the pointer to the object to the method. Who frees the object depends on the
design of the application. Remember FreeAndNil()? It takes a pointer to an
object and free the object. On the other hand, consider the following:

        TPerson = class
                Name: String;
                Age: Integer;
        End;
        TPersons = class(TList);
 
function SortPersonByAge(Item1, Item2: Pointer): Integer;
begin
        Result := TPerson(Item1).Age - TPerson(Item2).Age;
End;

Var
        Children: TPersons 
Begin
        Children := TPersons.Create();  // Assume its loads the children

        Children.Sort(SortPersonByAge);


Internally, TList.Sort implements a QuickSort algorithm to sort its
collection of items. It calls the supplies comparison function, passing two
items from the collection (pointers to the objects that is) and then
exchanges the items based on the result returned. 

 > 0 (positive) Item1 is less than Item2
   0                    Item1 is equal to Item2
 < 0 (negative) Item1 is greater than Item2

So just remember that objects are blocks of memory (that happen to be a
little smarter than records). They still need to be allocated and released
and their passed around via pointers. 

Scott Kellish


-----Original Message-----
From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] On Behalf
Of Alan Colburn
Sent: Friday, May 26, 2006 2:19 PM
To: Borland's Delphi Discussion List
Subject: Learning OOP (part 3)

My thanks to Rob, Wayne, Luthfi, and Scott for their responses to my last 
post. It was easy to synthesize what each of you said--quite instructive. If

it's OK by y'all, I'm going to keep asking occasional Q's on the topic. I 
find resources about object oriented design, but it's a little hard for me 
to translate them to Delphi. I've got a bunch of Q's below. Please don't 
feel compelled to respond to all of them :-)

First, I'm confused now about freeing memory and also about understanding 
what's passed, memory-wise, by parameters in methods ("x:TSomething" vs. 
"var x:TSomething" vs "const x:TSomething") ...

Next, to help me understand OOP, I made a small app. It simply manipulates 
attributes on a list of objects--books in a library, members of a club, 
whatever. I'm using people objects, but it could be anything. I could 
accomplish this task by dropping a few data aware controls on a form, 
connecting them to a dataset, and writing event handlers. To learn, I'm 
approaching the task in a more OOP-centric manner.

So, I created a base class (TPerson) with a few attributes, a class to work 
with groups of TPerson objects (TPeople), and a user interface class (TGUI).

I also created TPeople.SaveToFile and .LoadFromFile methods, but let's 
suppose I wanted to put the code into another class--someday I could decide 
to load/save info with a different file format, so it would be good to have 
persistence stuff separate from everything else.

Currently, the TGui class creates and ultimately frees a global instance of 
TPeople. TPeople creates a list of TPerson objects.

Assume that the Save code will iterate through the items in TPeople and save

each object's fields. So, I assume that TGui calls TPeople's SaveToFile 
method (passing in a filename string), which in turn calls a method in the 
new class (TPersistTheCollection).

Would TPeople.SaveToFile create a new instance of TPersistCollection, and 
iterate through a loop passing each member of the TPeople list to 
TPersistTheCollection (a pointer to the list member? a copy of the object?),

and then Free the instance?

And then how would the Load method work? What's passed from 
TPersistTheCollection?

Finally, are objects ever used as parameters in methods? If so, where are 
these object instances usually freed?

As always, my sincere thanks to you all. You're my teachers! -- Al C. 
_______________________________________________
Delphi mailing list -> [email protected]
http://www.elists.org/mailman/listinfo/delphi

_______________________________________________
Delphi mailing list -> [email protected]
http://www.elists.org/mailman/listinfo/delphi

Reply via email to