Comment libérer correctement des enregistrements qui contiennent différents types dans Delphi à la fois?

type
  TSomeRecord = Record
    field1: integer;
    field2: string;
    field3: boolean;
  End;
var
  SomeRecord: TSomeRecord;
  SomeRecAr: array of TSomeRecord;

C'est l'exemple le plus basique de ce que j'ai Et puisque je veux réutiliser SomeRecord (certains champs restant vides, sans libérer tout certains champs seraient transportés quand je réutilise SomeRecord , ce qui est évidemment indésirable) je cherche un moyen de libérer tous les champs à la fois. J'ai commencé avec string[255] et utilisé ZeroMemory() , ce qui était bien jusqu'à ce qu'il commence à perdre de la mémoire, c'est parce que je suis passé à string . J'ai encore manque de connaissances pour comprendre pourquoi, mais il semble être liée à elle étant dynamique. J'utilise aussi des tableaux dynamiques, donc je suppose qu'essayer ZeroMemory() sur quelque chose de dynamique entraînerait des fuites. Un jour gaspillé le comprendre. Je pense que j'ai résolu cela en utilisant Finalize() sur SomeRecord ou SomeRecAr avant ZeroMemory() , mais je ne suis pas sûr si c'est la bonne approche ou juste moi étant stupide.

donc la question est: comment libérer tout à la fois? n'certains procédure unique existe pour cela que je ne suis pas au courant?

sur une note différente, alternativement je serais ouvert à des suggestions sur la façon d'implémenter ces enregistrements différemment pour commencer, donc je n'ai pas besoin de faire des tentatives compliquées pour libérer des trucs. J'ai regardé dans la création d'enregistrements avec New() et puis se débarrasser de lui Dispose() , mais je n'ai aucune idée de ce que cela signifie Quand une variable après un appel à Dispose() est indéfinie, au lieu de zéro. Outre, Je ne sais pas quelle est la différence entre une variable d'un certain type ( SomeRecord: TSomeRecord ) et une variable pointant vers un type ( SomeRecord: ^TSomeRecord ). Je me penche sur les questions ci-dessus en ce moment, à moins que quelqu'un puisse l'expliquer rapidement, cela pourrait prendre un certain temps.

13
demandé sur Raith 2012-06-16 22:02:05

4 réponses

en supposant que vous avez une version Delphi qui supporte les méthodes d'implémentation sur un enregistrement, vous pouvez effacer un enregistrement comme ceci:

type
  TSomeRecord = record
    field1: integer;
    field2: string;
    field3: boolean;
    procedure Clear;
  end;

procedure TSomeRecord.Clear;
begin
  Self := Default(TSomeRecord);
end;

si votre compilateur ne supporte pas Default alors vous pouvez faire la même chose tout simplement comme ceci:

procedure TSomeRecord.Clear;
const
  Default: TSomeRecord=();
begin
  Self := Default;
end;

vous pourriez préférer éviter de muter un type de valeur dans une méthode. Auquel cas, créer une fonction qui renvoie un enregistrement vide de la valeur, et l'utiliser avec le opérateur d'affectation:

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;

entre parenthèses, Je ne trouve aucune référence documentaire pour Default(TypeIdentifier) . Personne ne sait où il peut être trouvé?


pour ce qui est de la deuxième partie de votre question, Je ne vois aucune raison de ne pas continuer à utiliser des enregistrements et à les allouer en utilisant des tableaux dynamiques. Tenter de gérer la durée de vie vous-même est beaucoup plus sujet à l'erreur.

23
répondu David Heffernan 2015-05-27 14:23:16

Ne pas faire pense être trop compliqué!

attribuer un" défaut " record est juste une perte de puissance CPU et de mémoire.

Lorsqu'un record est déclaré dans un TClass , il est rempli avec zéro, ainsi initialisé. Lorsqu'elle est attribuée sur la pile, seules les variables de référence comptées sont initialisées: d'autres types de variables (comme integer ou double ou booléens ou enumerations) sont dans un état aléatoire (probablement non zéro). Quand il sera alloué sur le tas, getmem n'initialisera rien, allocmem remplira tout le contenu avec zéro, et new initialisera seulement les membres comptés par référence (comme sur l'initialisation de la pile): dans tous les cas, vous devez utiliser soit dispose , soit finalize+freemem pour libérer un tas-alloué record .

donc à propos de votre question exacte, votre propre hypothèse était juste: pour réinitialiser un contenu d'enregistrement après utilisation, NE JAMAIS utiliser " fillchar " (ou " zeromemory ") sans un précédent" finalize ". Voici la voie correcte et la plus rapide:

Finalize(aRecord);
FillChar(aRecord,sizeof(aRecord),0);

encore une fois, ce sera plus rapide que d'assigner un enregistrement par défaut. Et dans tous les cas , si vous utilisez Finalize , même plusieurs fois, il ne fuira pas de mémoire - 100% garantie de remboursement!

Edit: après avoir regardé le code généré par aRecord := default(TRecordType) , le code est bien optimisé: il est en fait un Finalize + groupe de stosd pour émuler FillChar . Ainsi, même si la syntaxe est une copie / cession ( := ), elle n'est pas implémentée en tant que copie / cession. Mon erreur ici.

mais je n'aime toujours pas le fait qu'un := doit être utilisé, où Amarcadero aurait mieux utilisé une méthode record comme aRecord.Clear comme syntaxe, tout comme tableaux dynamiques de DelphiWebScript . En fait, cette syntaxe := est la même exact utilisé par C#. On dirait que si Embacardero imite juste la syntaxe C# partout, sans découvrir que c'est bizarre . Quel est l'intérêt si Delphi est juste un suiveur, et non implémenter pense "à sa façon"? Les gens préféreront toujours le C# original à son ancêtre (Delphi a le même père).

7
répondu Arnaud Bouchez 2012-06-17 18:48:31

la solution la plus simple à laquelle je pense sera:

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

mais pour répondre à toutes les autres parties de votre question, prenez ces définitions:

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;

et puis ici quelques exemples multiples sur la façon de fonctionner sur eux:

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

choisissez et / ou combinez comme vous le souhaitez.

5
répondu NGLN 2012-06-16 19:36:12

avec SomeRecord: TSomeRecord , SomeRecord sera une instance/variable de type TSomeRecord . Avec SomeRecord: ^TSomeRecord , SomeRecord sera un pointeur vers une instance ou une variable de type TSomeRecord . Dans le dernier cas, SomeRecord sera un pointeur typé. Si votre application transfère beaucoup de données entre routines ou interagit avec une API externe, un pointeur dactylographié est recommandé.

new() et dispose() sont utilisés uniquement avec des tapé des pointeurs. Avec des pointeurs dactylographiés le compilateur n'a pas le contrôle/la connaissance de la mémoire que votre application utilise avec ce genre de vars. C'est à vous de libérer la mémoire utilisée par tapé des pointeurs.

d'autre part, lorsque vous utilisez des variables normales, en fonction de l'utilisation et de la déclaration, le compilateur libèrera la mémoire utilisée par ces variables lorsqu'il considère qu'elles ne sont plus nécessaires. Par exemple:

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;

Dans l'exemple ci-dessus, la variable NativeVariable n'est utilisé dans la fonction SomeStaff , ainsi le compilateur le libère automatiquement lorsque la fonction se termine. Cette appy pour presque la plupart des variables natives, y compris les tableaux et les enregistrements "champs". Les objets sont traités différemment, mais c'est pour un autre post.

1
répondu Christopher Ramírez 2012-06-16 19:27:10