2012-06-16 17 views
13
type 
    TSomeRecord = Record 
    field1: integer; 
    field2: string; 
    field3: boolean; 
    End; 
var 
    SomeRecord: TSomeRecord; 
    SomeRecAr: array of TSomeRecord; 

Questo è l'esempio più semplice di quello che ho e perché voglio riutilizzare SomeRecord (con alcuni campi restanti vuoti, senza liberare tutto alcuni campi sarebbero stati riportati quando sono riutilizzando SomeRecord, che è ovviamente indesiderato) Sto cercando un modo per liberare tutti i campi contemporaneamente. Ho iniziato con string[255] e utilizzato ZeroMemory(), che andava bene fino a quando non ha iniziato a perdere memoria, perché ho passato a string. Mi manca ancora la conoscenza per ottenere il perché, ma sembra essere correlato al fatto che sia dinamico. Sto usando anche array dinamici, quindi presumo che provare ZeroMemory() su qualcosa di dinamico possa causare perdite. Un giorno sprecato a capirlo. Penso di averlo risolto utilizzando Finalize() su SomeRecord o SomeRecAr prima del , ma non sono sicuro che questo sia l'approccio corretto o solo che io sia stupido.Come liberare correttamente i record che contengono vari tipi in Delphi in una volta?

Quindi la domanda è: come liberare tutto in una volta? esiste una procedura unica per questo di cui non sono a conoscenza?

Su una nota diversa, in alternativa sarei aperto a suggerimenti su come implementare questi record in modo diverso per cominciare, quindi non ho bisogno di fare complicati tentativi di liberare materiale. Ho esaminato la creazione di record con New() e quindi di eliminarlo Dispose(), ma non ho idea di cosa significhi quando una variabile dopo una chiamata a Dispose() non è definita, invece di zero. Inoltre, non so qual è la differenza tra una variabile di un certo tipo (SomeRecord: TSomeRecord) rispetto a una variabile che punta a un tipo (SomeRecord: ^TSomeRecord). Al momento sto esaminando i problemi di cui sopra, a meno che qualcuno non possa spiegarlo rapidamente, potrebbe volerci del tempo.

risposta

23

Supponendo di avere una versione di Delphi che supporta metodi su un record di attuazione, si potrebbe cancellare un disco come questo:

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

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

Se il compilatore non supporta Default allora si può fare lo stesso semplicemente come questo:

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

si potrebbe preferire di evitare di mutare un tipo di valore in un metodo. In questo caso creare una funzione che restituisce un valore record vuoto, e utilizzarlo con l'operatore di assegnazione:

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; 

Per inciso, non riesco a trovare alcun riferimento documentazione per Default(TypeIdentifier). Qualcuno sa dove può essere trovato?


Per quanto riguarda la seconda parte della tua domanda, non vedo alcun motivo per non continuare a utilizzare i record, e di assegnazione utilizzando gli array dinamici. Tentare di gestire la vita da solo è molto più incline agli errori.

+1

@NGLN Mi rendo conto che l'essenza di questa risposta è ciò che hai scritto in origine. Penso che sia stata una risposta eccellente. A mio parere è un peccato che tu l'abbia rimosso. Se hai ripristinato una risposta del genere, eliminerei questo e il tuo voto. Ma penso che questa domanda dovrebbe avere una risposta in questo senso. Preferirei che fosse tuo da quando eri il primo. –

+1

concordato. Nessun duro, terrò il mio com'è adesso. – NGLN

+0

@NGLN OK grazie per la comprensione e ho rimosso il mio downvote, che è stato duro. –

5

La più semplice soluzione penso sarà:

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

Ma per affrontare tutte le restanti parti della tua domanda, prendere queste definizioni:

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; 

E allora ecco alcuni esempi multipli su come operare su di loro:

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

Scegliere e/o combinare come si desidera.

+0

quindi stai suggerendo di impostare 'SomeRecord' su' EmptyRecord' ogni volta che voglio cancellarlo? Questo è abbastanza semplice, credo, ma è davvero il modo giusto? Il record in questione ha molti più campi, diciamo alcune centinaia più campi con tipi personalizzati, non è impensabile dichiarare un tale EmptyRecord, ma sembra poco pratico se si considera la dimensione della dichiarazione costante. – Raith

+4

Una definizione più flessibile della costante sarebbe 'EmptySomeRecord: TSomeRecord =();' e lascia che il compilatore riempia gli zeri. –

+0

@ David: Grazie! – NGLN

1

Con SomeRecord: TSomeRecord, SomeRecord sarà un'istanza/variabile di tipo TSomeRecord. Con SomeRecord: ^TSomeRecord, SomeRecord sarà un puntatore a un'istanza o variabile di tipo TSomeRecord. Nell'ultimo caso, SomeRecord sarà un puntatore digitato. Se l'applicazione trasferisce molti dati tra le routine o interagisce con l'API esterna, si consiglia di utilizzare il puntatore digitato.

new() e dispose() vengono utilizzati solo con puntatori digitati. Con i puntatori digitati il ​​compilatore non ha controllo/conoscenza della memoria utilizzata dall'applicazione con questo tipo di vars. Spetta a te liberare la memoria utilizzata dai puntatori digitati.

D'altra parte, quando si usano variabili normali, a seconda dell'uso e della dichiarazione, il compilatore libera la memoria utilizzata da questi quando ritiene che non siano più necessari. Ad esempio:

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; 

Nell'esempio precedente, la variabile NativeVariable viene utilizzato solo all'interno della funzione SomeStaff, quindi il compilatore automaticamente libera quando la funzione termina. Questo vale per quasi tutte le variabili native, inclusi array e record "campi". Gli oggetti sono trattati in modo diverso, ma questo è per un altro post.

6

Non farti pensare eccessivamente complicato!

Assegnare un valore "predefinito" record è solo una perdita di potenza e memoria della CPU.

Quando uno record viene dichiarato all'interno di uno TClass, viene riempito con zero, quindi inizializzato. Quando è allocato sullo stack, vengono inizializzate solo le variabili conteggiate di riferimento: altri tipi di variabile (come intero o doppio o booleani o enumerazioni) sono in uno stato casuale (probabilmente non zero). Quando verrà allocata nell'heap, getmem non inizializzerà nulla, allocmem riempirà tutto il contenuto con zero e new inizializzerà solo i membri conteggiati di riferimento (come nell'inizializzazione dello stack): in tutti i casi, è necessario utilizzare dispose, finalize+freemem per rilasciare un heap assegnato record.

Quindi sulla tua domanda esatta, il proprio presupposto aveva ragione: per ripristinare un contenuto di record dopo l'uso, non usare mai "fillchar" (o "zeromemory ') senza una precedente' finalize". Ecco il modo corretto e più veloce:

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

Ancora una volta, sarà più veloce dell'assegnazione di un record predefinito. E in ogni caso, se usi Finalize, anche più volte, non perderà più memoria - garanzia di rimborso al 100%!

Edit: Dopo aver guardato il codice generato da aRecord := default(TRecordType), il codice è ben ottimizzato: si tratta infatti di un Finalize + mucchio di stosd per emulare FillChar. Pertanto, anche se la sintassi è una copia/assegnazione (:=), non è implementata come copia/assegnazione. Il mio errore qui.

Ma io ancora non mi piace il fatto che un := deve essere utilizzato, dove Embarcadero dovrebbe avere una migliore utilizzato un metodo record come aRecord.Clear come sintassi, proprio come DelphiWebScript's dynamic arrays. In effetti, questa sintassi := è la stessa esatta usata da C#. Sembra che Embacardero imiti la sintassi C# ovunque, senza scoprire che è this is weird. Qual è il punto se Delphi è solo un seguace, e non implementare pensa "a modo suo"? Le persone preferiranno sempre l'originale C# al suo antenato (Delphi ha lo stesso padre).

+0

Perché un così "-1" orgoglioso senza commenti? ;) –

+0

Il tuo down-voter ovviamente ha cambiato idea, ma un downvote sarebbe meritato qui. Prima di tutto, le prestazioni non sono probabilmente il fattore più importante per l'OP. Ma ancora più importante, le tue affermazioni qui non sono corrette. 'aRecord: = Default (TSomeRecord)' è efficiente. –

+0

@DavidHeffernan Certo, non è morto lento. Ma in definitiva Finalize + Copy sarà più lento di Finalize + FillChar. E un compito è solo confuso qui. Vedi il mio commento nella tua risposta. Il record di copia è lontano dall'ottimizzazione in Delphi: usa RTTI, e non il codice ottimizzato per il compilatore - vedi http://blog.synopse.info/post/2010/03/23/CopyRecord-faster-proposal –

Problemi correlati