How to properly free records that contain various types in Delphi at once?
type
TSomeRecord = Record
field1: integer;
field2: string;
field3: boolean;
End;
var
SomeRecord: TSomeRecord;
SomeRecAr: array of TSomeRecord;
This is the most basic example of what I have and since I want to reuse SomeRecord
(with certain fields remaining empty, without freeing everything some fields would be carried over when I'm reusing SomeRecord
, which is obviously undesired) I am looking for a way to free all of the fields at once. I've started out with string[255]
and used ZeroMemory()
, which was fine until it started leaking memory, that was because I switched to string
. I still lack the knowledge to get why, but it appears to be related to it being dynamic. I am using dynamic arrays as well, so I assume that trying ZeroMemory()
on anything dynamic would result in leaks. One day wasted figuring that out. I think I solved this by using Finalize()
on SomeRecord
or SomeRecAr
before ZeroMemory()
, but I'm not sure if this is the proper approach or just me being stupid.
So the question is: how to free everything at once? does some single procedure exist at all for this that I'm not aware of?
On a different note, alternatively I would be open to suggestions how to implement these records differently to begin with, so I don't need to make complicated attempts at freeing stuff. I've looked into creating records with New()
and then getting rid of it Dispose()
, but I have no idea what it means when a variable after a call to Dispose()
is undefined, instead of nil. In addition, I don't know what's the difference between a variable of a certain type (SomeRecord: TSomeRecord
) versus a variable pointing to a type (SomeRecord: ^TSomeRecord
). I'm looking into the above issues at the moment, unless someone can explain it quickly, it might take some time.
Solution 1:
Assuming you have a Delphi version that supports implementing methods on a record, you could clear a record like this:
type
TSomeRecord = record
field1: integer;
field2: string;
field3: boolean;
procedure Clear;
end;
procedure TSomeRecord.Clear;
begin
Self := Default(TSomeRecord);
end;
If your compiler doesn't support Default
then you can do the same quite simply like this:
procedure TSomeRecord.Clear;
const
Default: TSomeRecord=();
begin
Self := Default;
end;
You might prefer to avoid mutating a value type in a method. In which case create a function that returns an empty record value, and use it with the assignment operator:
type
TSomeRecord = record
// fields go here
class function Empty: TSomeRecord; static;
end;
class function TSomeRecord.Empty: TSomeRecord;
begin
Result := Default(TSomeRecord);
end;
....
Value := TSomeRecord.Empty;
As an aside, I cannot find any documentation reference for Default(TypeIdentifier)
. Does anyone know where it can be found?
As for the second part of your question, I see no reason not to continue using records, and allocating them using dynamic arrays. Attempting to manage the lifetime yourself is much more error prone.
Solution 2:
Don't make thinks overcomplicated!
Assigning a "default" record
is just a loss of CPU power and memory.
When a record
is declared within a TClass
, it is filled with zero, so initialized. When it is allocated on stack, only reference counted variables are initialized: others kind of variable (like integer or double or booleans or enumerations) are in a random state (probably non zero). When it will be allocated on the heap, getmem
will not initialize anything, allocmem
will fill all content with zero, and new
will initialize only reference-counted members (like on the stack initialization): in all cases, you should use either dispose
, either finalize+freemem
to release a heap-allocated record
.
So about your exact question, your own assumption was right: to reset a record content after use, never use "fillchar
" (or "zeromemory
") without a previous "finalize
". Here is the correct and fastest way:
Finalize(aRecord);
FillChar(aRecord,sizeof(aRecord),0);
Once again, it will be faster than assigning a default record. And in all case, if you use Finalize
, even multiple times, it won't leak any memory - 100% money back warranty!
Edit: After looking at the code generated by aRecord := default(TRecordType)
, the code is well optimized: it is in fact a Finalize
+ bunch of stosd
to emulate FillChar
. So even if the syntax is a copy / assignement (:=
), it is not implemented as a copy / assignment. My mistake here.
But I still do not like the fact that a :=
has to be used, where Embarcadero should have better used a record
method like aRecord.Clear
as syntax, just like DelphiWebScript's dynamic arrays. In fact, this :=
syntax is the same exact used by C#. Sounds like if Embacardero just mimics the C# syntax everywhere, without finding out that this is weird. What is the point if Delphi is just a follower, and not implement thinks "its way"? People will always prefer the original C# to its ancestor (Delphi has the same father).