I have a major software package used by veterinary clinics, and have never used pointers in it.
I would like to know is there is an advantage to using pointers, and if so, where? A major part
of the app is invoicing. Each time an invoice is opened I store the
Clients last name
Clients first name
Clients number
Patient's (pet) name
Patient's number
to variables and use them frequently as the invoice is built and posted. Where in this process
can I (and should I) use pointers? Also, as I read on pointers I see a lot of
GetMem and
FreeMem
stuff. But I see in the Delphi help that the use of
New and
Dispose
is preferred. I would appreciate any insight anyone could give me on this subject.
Thanks,
Jim Sawyer
No specific problem. I'm mostly trying to get a better handle on using pointers and when it is beneficial to do so.
I guess I'd better also brush up on when and how to use CREATE and FREE.
Dawson! I grew up in Dawson, TX.
> {quote:title=Robert Dawson wrote:}{quote}
> wrote
> > I would like to know is there is an advantage to using pointers, and if
> > so, where?
>
> Depending on context, there could be, but what you're mentioning sounds more
> like potential class structures than an occasion pointer use. Given a class,
> instance creation would generally by done using Create and Free calls.
>
> Is there a specific problem or problems you're trying to address?
>
> bobD
How do I free the memory in the following example:
procedure TForm.btnTestClick( Sender );
type
TLoggedClient = record
CL: String;
CF: String;
CN: String;
end;
var
LoggedClient: TLoggedClient;
begin
LoggedClient.CL := 'Samples';
LoggedClient.CF := 'Junior';
LoggedClient.CN := 'BR549';
ShowMessage( LoggedClient.CF + ' ' + LoggedClient.CL + ', ' + LoggedClient.CN );
end;
How do I free the memory after showing the message?
Thanks,
Jim Sawyer
> {quote:title=Robert Dawson wrote:}{quote}
> wrote
> > No specific problem. I'm mostly trying to get a better handle on using
> > pointers and when it is beneficial to do so.
>
> Serious use now would be high speed computational engines--complex
> simulations, encryption, low level drivers--that sort of thing.
>
> By contrast, anywhere where the system is interacting with a user or
> peripheral (printer, screen, database, harddrive, etc.) the latency of those
> operations is orders of magnitudes beyond what the difference between getmem
> or new is going to be. Spped today, in general business computing, is driven
> more by the domain model efficiency and algorithm choices than low-level
> coding techniques.
>
> > I guess I'd better also brush up on when and how to use CREATE and FREE.
>
> This is as good a place as any for getting started with classes.
>
> > Dawson! I grew up in Dawson, TX.
>
> There's a Dawson, Alaska, too. No family history through either, though.
> Have lived in Killeen and Kingsville, Tx.
>
> bobD
I'm in the process of implementing the stuff you folks taught me below, during which I came across
a conumdrum that begs the following question:
I actually have multiple locations where the same TObject needs to be created. Is there a way to
test to see of the TObject has been created and has not yet been FREEd? This would make
life a lot easier.
Thanks,
Jim Sawyer
Thank you so much! You have done a great job of clarifying this for me. Now to
get proficient with it...
Jim Sawyer
> {quote:title=Wayne Niddery wrote:}{quote}
> "Jim Sawyer" wrote in message news:679647@forums.embarcadero.com...
> > How do I free the memory in the following example:
> >
> > procedure TForm.btnTestClick( Sender );
> > type
> > TLoggedClient = record
> > CL: String;
> > CF: String;
> > CN: String;
> > end;
> > var
> > LoggedClient: TLoggedClient;
> > begin
> > LoggedClient.CL := 'Samples';
> > LoggedClient.CF := 'Junior';
> > LoggedClient.CN := 'BR549';
> > ShowMessage( LoggedClient.CF + ' ' + LoggedClient.CL + ', ' +
> > LoggedClient.CN );
> >
> > end;
> >
> > How do I free the memory after showing the message?
>
>
> Here you have used a *record* not a class. As a result, the LoggedClient
> variable is allocated for you (on the stack) as a local variable when you
> enter the procedure and deallocated automatically when you exit the
> procedure. There is nothing for you to manually create or free.
>
> If you define LoggedClient as a *Class* then you would need to both create
> and free it and the proper code structure for it, in this example, would be:
>
> LoggedClient := TLoggedClient.Create;
> try
> LoggedClient.CL := 'Samples';
> LoggedClient.CF := 'Junior';
> LoggedClient.CN := 'BR549';
> ShowMessage( LoggedClient.CF + ' ' + LoggedClient.CL + ', ' +
> LoggedClient.CN );
> finally
> LoggedClient.Free;
> end;
>
> If your use of either records or classes is going to be limited to local
> variables in a procedure like this then there is no real call for them,
> continue to simply use variables like
> firstname: string;
> etc.
>
> However, as soon as you have a need for such information to persist over
> more than one procedure or function, or you need to pass the information
> around from one procedure to another or one class to another, then it pays
> to define such records or classes.
>
> If you only have a need for a single instance of such a structure, then a
> record will do. If you need to allow for multiple instances then, while
> records *can* still be used, classes are better and easier.
>
> So for example, if you are only ever working with a single client at a time
> in a form then this might do:
>
> type
> TLoggedClient = record
> CL: String;
> CF: String;
> CN: String;
> end;
>
> TForm1 = class
> [...]
> private
> LoggedClient: TLoggedClient;
> end;
>
> Then in one of your methods of this form you will assign the values to that
> record, and all other methods and events of the form can then see those
> values, and if you need to pass it to, say, another form, you only need to
> pass the LoggedClient variable, not the separate CL,CF, and CN fields.
>
> Note also that, whether a record or class, you can add methods. E.g.
>
> TLoggedClient = record
> CL: String;
> CF: String;
> CN: String;
> function FullName: string;
> end;
>
> implementation
>
> function TLoggedClient.FullName: string;
> begin
> Result := CL + ', ' + CF;
> end;
>
> ...
> ShowMessage(LoggedClient.FullName);
>
> --
> Wayne Niddery
> "You know what they call alternative medicine that has been proven to work?
> Medicine." - Tim Minchin
Edited by: Jim Sawyer on Jun 9, 2014 11:44 AM
I did not use very good terminology in my last question. Let me try again.
I have two objects. The first is TLoggedClient and the other is TLoggedPatient.
For the invoicing process, I create LCli from TLoggedClient in which I store the
client's information for use during the tangled tenacles of the invoicing process,
and I also create LPat from TLoggedPatient in which I store the patient's (pet's)
information. It is possible (necessary) that I initiate the invoicing process from
multiple locations (logging for the client/patient grids, from the appointment
calendar, when automatically converting an estimate to an invoice, etc) So when
I create LCli and LPat, I would like to test to determine that they haven't already
been created.
After the post yesterday, I started using
if not Assigned( LCli ) then
LCli := TLoggedClient.Create;
if not Assigned( LPat ) then
LPat := TLoggedPatient.Create;
Preliminary testing indicates that those bits of code may suffice.
Let me say that I have been marketing this system in Delphi since
1995 and have been a pretty good programming rut and have never
had programming training. As I learned this concept yesterday (at
77 years old I might add) I felt like a whole new tool package became
available. Much like when I discovered DevExpress components
and LayOut package. Thanks for you guys help in that process.
> {quote:title=Robert Dawson wrote:}{quote}
> wrote
> >
> > I actually have multiple locations where the same TObject needs to be
> > created.
>
> I have the same question as John---exactly what does "the same TObject"
> mean?
>
> It sounds like you're thinking in terms of records, where generally one
> record declaration means one instance. You have to think in terms of class
> as a declaration without an instance, and specific instance, since TClient
> is a structure representative of all clients but doesn't accully get
> instantiated until you create it, whereas a class instance can exist even
> after the variable pointing to it goes out of scope:
>
> var.
> myClient TClient;
> begin
> myClient := TClient.Create('Joe', 'Green', 1);
> end;
> //memory leak--Joe Green still exists on the heap, but it's no longer
> available
>
> Specific questions:
> 1. Is it possible to have Joe Green and Joe Stream both in memory at the
> same time?
> 2. If not, what happens when you're working with Joe Green, but need to
> change to Joe Stream?
> 3. If so (more than one client instance can exist), then how does the user
> code know which instance to create or request?
>
> The classic way to handle the situation where access to a specific instance
> has to be obtained from multiple locations in the program would be a
> factory: the factory tracks the status of the instance(s), and all users ask
> the factory for access rather than creating the instance they need
> themselves.
>
> bobD
> {quote:title=Jim Sawyer wrote:}{quote}
>>
> if not Assigned( LCli ) then
> LCli := TLoggedClient.Create;
> if not Assigned( LPat ) then
> LPat := TLoggedPatient.Create;
What's the scope of the LCli and LPat variables? (IOW, where are they declared?) Are they single variables, visible globally?
bobD
LCli and LPat are created when an invoice is opened and remain until the invoice is either posted or closed for work on
a different invoice. LCli is constant for an invoice since there is only one client (owner), but since the invoice can involve
more than one of the owner's pets, LPat changes to represent the patient on which attention is being focused at any
given time. Services, products, and diagnosises will be given from different pick-lists (window with a grid), so as one
pet gets attention LPat points to that patient. LCli and LPat continue in action through invoice posting which includes
updating medical records, creating an invoice record, printing invoice, vaccination sheet, report card, check-out sheet,
and finally, when this is completed, the memory is freed. Of course, several dozen invoices are switched between
frequently, so they are freed and re-created for another client/patient often without completing the invoice. But a workstation
will maintain only one LCli and one LPat (although it's value may change frequently in the multiple-patient case) at a time.
Define "visible globally". The values are visible only to one operator on one workstation. If two workstations were
maintaining the same invoice at the same time, they would have similar values, but would be created and freed
independent of each other.
Jim Sawyer
> {quote:title=Robert Dawson wrote:}{quote}
> > {quote:title=Jim Sawyer wrote:}{quote}
> >>
> > if not Assigned( LCli ) then
> > LCli := TLoggedClient.Create;
> > if not Assigned( LPat ) then
> > LPat := TLoggedPatient.Create;
>
> What's the scope of the LCli and LPat variables? (IOW, where are they declared?) Are they single variables, visible globally?
>
> bobD
I Define the types in a wrapper visible from anywhere in the program. I generate
an instance where LCli's 3 variables contain the client's last name, the client's firstname,
and the client number. LPat's 2 variables contain the patient's name and number.
I can generate the instances from
1. The client/patient screen (master/slave grids where patient is slave)
2. The patient screen (master/slave grids where the client is slave)
3. The appointment calendar when an appointment shows up
4. The boarding map when the patient is ready to go home
5. From the client patient screen for the generation of an estimate to keep on file
6. From the estimate screen when the estimate is to automatically convert to an invoice.
It's sometimes a bit spaghettiish.
Thanks
Jim Sawyer
> {quote:title=Robert Dawson wrote:}{quote}
> wrote in message news:680054@forums.embarcadero.com...
> > LCli and LPat are created when an invoice is opened and remain until the
> > invoice is
> > either posted or closed for work on a different invoice.
>
> Is there anywhere other than within the context of an invoice where an LCli
> is created?
> After the line
> LCli := TLoggedClient.Create;
> ---how does the LCli structure obtain its data? (FName, LName, etc)
> ---does that data ever change within the context of the invoicing?
> ---does the LCli ever _do_ anything within the context of the invoicing
> (IOW, have methods/operations called on it, other than to read its
> data/properties? (sounds like no)
>
> [...]
> > a workstation will maintain only one LCli and one LPat (although it's
> > value may change
> > frequently in the multiple-patient case) at a time.
>
> Generally for an OO system, there's going to be a
> 1::1 relationship between a class instance and the real world object that
> that instance represents. For example, IIRC your system has a Patient ID
> field; that's presumably unique within the scope of all patients for the
> installation. You might then create an LPat patient instance by that
> identifier
> patient := TLoggedPatient.Create(;
> and on the instance itself the ID property would be readonly--not possible
> to change.
>
> Besides the invoicing function, in what other major systems would these
> classes play a role?
> Does each have it's own declaration of an LCli and LPat variable?
> Does each have it's own code for filling their values?
>
> > Define "visible globally".
>
> For example, there's a unit for the Client class looking something like
>
> unit LoggedClient
>
> interface
>
> type
> TLoggedClient = class(TObject)
> //
> end;
>
> var
> client : TLoggedClient
>
> implementation
> // etc.
>
> This is essentially the default declaration pattern for Delphi forms.
>
> bobD
Edited by: Jim Sawyer on Jun 13, 2014 5:43 AM
I'm trying to wrap my noggin around this new (potential) knowledge. I assume
you are saying that the initial action of "logging a patient" would CREATE then
instance which would be FREEd at the bottom of that action. That would provide
some reliable variables for filling up with separate code. I also assume there is
no problem for me changing the value of the PAT (patient) variable during the
processing of a multi-patient invoice.
I'm also trying to figure out where I could make use of a function included in
the type. Would it be possible to use VAR parameters in such a function (or
maybe a procedure) so it could maintain multiple variables? I hope this
question makes sense!
Thanks
Jim Sawyer
> {quote:title=Robert Dawson wrote:}{quote}
> wrote
> > I can generate the instances from
> > 1. The client/patient screen (master/slave grids where patient is slave)
> > 2. The patient screen (master/slave grids where the client is slave)
> > 3. The appointment calendar when an appointment shows up
> > 4. The boarding map when the patient is ready to go home
> > 5. From the client patient screen for the generation of an estimate to
> > keep on file
> > 6. From the estimate screen when the estimate is to automatically convert
> > to an invoice.
>
> Does each calling location fill the fields? If so, that's an area you could
> simplify by moving the responsibility for setting up the Cli/Pat objects
> into their own subsystem. The front end functions/systems request the
> objects, but they don't have any knowledge of their internal set-up process
> or data source. They're just guaranteed that, when they need instance
> , they get it, and that's done correctly because there's only
> one place in the program that that actually happens.
>
> bobD
I have defined in a wrapper
type
str24 = string[24];
str16 = string[16];
str8 = string[8];
TLogged = class(TObject)
CF: Str24;
CL: Str24;
CN: Str8;
Pat: Str16;
Num: Str8;
end;
TFactory = class( TObject )
CP: Boolean;
end;
var
Factory: TFactory;
Lcp: TLogged;
As the program is opened I have
Factory := TFactory.Create
and as it closes I
Factory.Free
so it is available for use all during the execution of the program.
Now in the program where ever I need to start using the Lcp I do something like
if not Factory.CP then
Factory.CP := true;
Lcp := TLogged.Create;
end
Lcp.CL := ClientTSet.FieldAsString('Lastname');
Lcp:CF := ClientTSet.FieldAsString('Firstname');
Lcp.CN := ClientTSet.FieldAsString('Clino');
Lcp.Pat := PatientTSet.FieldAsString('Name');
Lcp.Num := PatientTSet.FieldAsString('Patno');
// going into the wilderness where these values will be used
OpenForm( TfrmInvoice, frmInvoice ); // this is just a procedure Ive defined for opening a form
//then when finally when this process is complete and exited
if Factory.CP then
begin
Factory.XP := false;
Lcp.Free;
end;
Thanks,
Jim Sawyer
> {quote:title=Robert Dawson wrote:}{quote}
> wrote
> > you are saying that the initial action of "logging a patient" would CREATE
> > then
> > instance which would be FREEd at the bottom of that action.
>
> An object instance created in a procedure needn't be freed in that
> procedure. Can be, but not a rule.
>
> Let's start w/ a simple case as you currently do it. Something like this I
> assume:
>
> var
> client : TLoogedClientRecord;
> begin
> client.FirstName := ?? Where do client records get their values from?
> // business code for doing something with client...
>
> I'm quessing that you're loading from a dataset, since you mentioned using
> the DevExpress grid. That right? Show me how clients are currently loaded.
> I'll show you some options from there.
>
> bobD