How to properly free records that contain various types in Delphi at once?

后端 未结 4 605
北海茫月
北海茫月 2020-11-29 19:47
type
  TSomeRecord = Record
    field1: integer;
    field2: string;
    field3: boolean;
  End;
var
  SomeRecord: TSomeRecord;
  SomeRecAr: array of TSomeRecord;


        
相关标签:
4条回答
  • 2020-11-29 20:24

    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).

    0 讨论(0)
  • 2020-11-29 20:30

    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.

    0 讨论(0)
  • 2020-11-29 20:41

    The most simply solution I think of will be:

    const
      EmptySomeRecord: TSomeRecord = ();
    begin
      SomeRecord := EmptySomeRecord;
    

    But to address all the remaining parts of your question, take these definitions:

    type
      PSomeRecord = ^TSomeRecord;
      TSomeRecord = record
        Field1: Integer;
        Field2: String;
        Field3: Boolean;
      end;
      TSomeRecords = array of TSomeRecord;
      PSomeRecordList = ^TSomeRecordList;
      TSomeRecordList = array[0..MaxListSize] of TSomeRecord;    
    const
      EmptySomeRecord: TSomeRecord = ();
      Count = 10;    
    var
      SomeRecord: TSomeRecord;
      SomeRecords: TSomeRecords;
      I: Integer;
      P: PSomeRecord;
      List: PSomeRecordList;
    
    procedure ClearSomeRecord(var ASomeRecord: TSomeRecord);
    begin
      ASomeRecord.Field1 := 0;
      ASomeRecord.Field2 := '';
      ASomeRecord.Field3 := False;
    end;
    
    function NewSomeRecord: PSomeRecord;
    begin
      New(Result);
      Result^.Field1 := 0;
      Result^.Field2 := '';
      Result^.Field3 := False;
    end;
    

    And then here some multiple examples on how to operate on them:

    begin
      // Clearing a typed variable (1):
      SomeRecord := EmptySomeRecord;
    
      // Clearing a typed variable (2):
      ClearSomeRecord(SomeRecord);
    
      // Initializing and clearing a typed array variabele:
      SetLength(SomeRecords, Count);
    
      // Creating a pointer variable:
      New(P);
    
      // Clearing a pointer variable:
      P^.Field1 := 0;
      P^.Field2 := '';
      P^.Field3 := False;
    
      // Creating and clearing a pointer variable:
      P := NewSomeRecord;
    
      // Releasing a pointer variable:
      Dispose(P);
    
      // Creating a pointer array variable:
      ReallocMem(List, Count * SizeOf(TSomeRecord));
    
      // Clearing a pointer array variable:
      for I := 0 to Count - 1 do
      begin
        Pointer(List^[I].Field2) := nil;
        List^[I].Field1 := 0;
        List^[I].Field2 := '';
        List^[I].Field3 := False;
      end;
    
      // Releasing a pointer array variable:
      Finalize(List^[0], Count);
    

    Choose and/or combine as you like.

    0 讨论(0)
  • 2020-11-29 20:45

    With SomeRecord: TSomeRecord, SomeRecord will be an instance/variable of type TSomeRecord. With SomeRecord: ^TSomeRecord, SomeRecord will be a pointer to a instance or variable of type TSomeRecord. In the last case, SomeRecord will be a typed pointer. If your application transfer a lot of data between routines or interact with external API, typed pointer are recommended.

    new() and dispose() are only used with typed pointers. With typed pointers the compiler doesn't have control/knowlegde of the memory your application is using with this kind of vars. It's up to you to free the memory used by typed pointers.

    In the other hand, when you use a normal variables, depending on the use and declaration, the compiler will free memory used by them when it considers they are not necessary anymore. For example:

    function SomeStaff();
    var
        NativeVariable: TSomeRecord;
        TypedPointer: ^TSomeRecord;
    begin
        NaviveVariable.Field1 := 'Hello World';
    
        // With typed pointers, we need to manually
        // create the variable before we can use it.
        new(TypedPointer);
        TypedPointer^.Field1 := 'Hello Word';
    
        // Do your stuff here ...
    
        // ... at end, we need to manually "free"
        // the typed pointer variable. Field1 within
        // TSomerecord is also released
        Dispose(TypedPointer);
    
        // You don't need to do the above for NativeVariable
        // as the compiler will free it after this function
        // ends. This apply also for native arrays of TSomeRecord.
    end;
    

    In the above example, the variable NativeVariable is only used within the SomeStaff function, so the compiler automatically free it when the function ends. This appy for almost most native variables, including arrays and records "fields". Objects are treated differently, but that's for another post.

    0 讨论(0)
提交回复
热议问题