2009-04-23 6 views
7

Nel mio codice uso una piccola classe di memorizzazione dei dati, creata in luoghi diversi. Per evitare perdite di memoria e di semplificare le cose, voglio usare il conteggio dei riferimenti, così ho fattoConteggio di riferimento per oggetti

type TFileInfo = class (TInterfacedObject, IInterface) 

e rimosso tutte le mie chiamate manuali ai TFileInfo.Free. Sfortunatamente Delphi ha riportato molte perdite di memoria. Cercando sul così ho trovato la seguente questione spiegando perché questo non funziona:

Why aren't descendants of TInterfacedObject garbage collected?

C'è una soluzione presentata lì, ma mi richiede (almeno se ho capito bene) per scrivere un'interfaccia personalizzata IFileInfo e fornirlo con un sacco di getter e setter, che voglio evitare.

EDIT Vorrei aggiungere che inserisco il creare FileInfo oggetti in due diversi tipi di tabelle hash: una discendente da TBucketList e un altro è un'implementazione hash mappa dal forum CodeGear. Internamente entrambi i puntatori degli utenti, quindi la situazione è come nell'altra domanda.

C'è qualche altra possibilità di creare oggetti in Delphi usando il conteggio dei riferimenti?

risposta

5

Purtroppo, il compilatore Delphi genera il codice necessario per inc conteggio di riferimento/dicembre solo quando si utilizza interfacce (nel tuo caso interfaccia personalizzata IFileInfo). Inoltre, se le interfacce sono gettate sul puntatore (o TObject per quella materia), di nuovo non è possibile il conteggio dei riferimenti. Ad esempio, assume di elenco globale variabile: TList:

var ifi : IFileInfo; 
begin 
    ifi := TFileInfo.Create; 
    list.Add(TFileInfo(ifi)); 
end; 

dopo la lista metodo restituisce [list.Count - 1] conterrà penzoloni puntatore.

Per cui le interfacce non possono essere utilizzate in una mappa di hash che li inserisce in puntatori, l'implementazione di hashmap deve mantenerli come IInterface.

+0

Ma il compilatore genera il corretto inc/dec del tutto se inserisco tutti gli oggetti in una mappa hash? – jpfollenius

+0

risposta modificata con informazioni aggiuntive – Kcats

+0

Questo codice non viene compilato. Non è possibile connettere le interfacce agli oggetti. – dummzeuch

2

Questa funzionalità viene fornita per le interfacce ma non per gli oggetti.

È possibile creare qualcosa di simile, ma è necessario sostituire alcune delle strutture di TObject:

TRefCountObject = class (TObject) 
private 
    FRefCount : Integer; 
public 
    constructor Create; 

    procedure Free; reintroduce; 

    function RefCountedCopy: TRefCountObject; 
end; 


constructor TRefCountObject.Create; 
begin 
    inherited; 
    FRefCount := 1; 
end; 

procedure TRefCountObject.Free; 
begin 
    if self=nil then Exit; 
    Dec(FRefCount); 
    if FRefCount<=0 then 
    Destroy; 
end; 

function TRefCountObject.RefCountedCopy: TRefCountObject; 
begin 
    Inc(FRefCount); 
    Result := self; 
end; 

È necessario RefCountedCopy per assegnare l'oggetto a un'altra variabile. Ma poi hai un oggetto conteggiato.

Come utilizzare questo:

var1 := TRefCountObject.Create; // rc = 1 
var2 := var1.RefCountedCopy;  // rc = 2 
var3 := var1.RefCountedCopy;  // rc = 3 
var2.Free;      // rc = 2 
var1.Free;      // rc = 1 
var4 := var3.RefCountedCopy;  // rc = 2 
var3.Free;      // rc = 1 
var4.Free;      // rc = 0 
+0

Grazie per la risposta dettagliata! Non lo capisco completamente però. Devo ancora chiamare TRefCountObject.Free giusto? O come lo uso? – jpfollenius

+0

Aggiunte alcune informazioni sull'utilizzo. –

+0

Quindi devo ancora assicurarmi di chiamare Free almeno una volta per ogni oggetto, giusto? E non c'è modo di evitarlo? – jpfollenius

0

C'è una lunga spiegazione a questo, ma in breve: Ereditare da TInterfacedObject (e non chiamare Free te stesso), non è sufficiente, devi usare un oggetto-factory-dynamic per creare gli oggetti per te, e usare l'interfaccia -indicatori sull'oggetto ovunque, non solo sulle variabili di riferimento dell'oggetto. (Sì, ciò significa che non puoi semplicemente passare "vecchio codice" senza guardarlo)

3

Non mescolare riferimenti a oggetti e riferimenti di interfaccia.

var 
    Intf: IInterface; 
    Obj: TFileInfo; 

begin 
    // Interface Reference 
    Intf := TFileInfo.Create; // Intf is freed by reference counting, 
          // because it's an interface reference 
    // Object Reference 
    Obj := TFileInfo.Create; 
    Obj.Free; // Free is necessary 

    // Dangerous: Mixing 
    Obj := TFileInfo.Create; 
    Intf := Obj; // Intf takes over ownership and destroys Obj when nil! 
    Intf := nil; // reference is destroyed here, and Obj now points to garbage 
    Obj.Free; // this will crash (AV) as Obj is not nil, but the underlying object 
      // is already destroyed 
end; 
8

Il conteggio dei riferimenti in Delphi funziona solo se si dispone di un riferimento alla propria istanza tramite un'interfaccia. Non appena mischi i riferimenti all'interfaccia e i riferimenti di classe, allora sei nei guai.

In sostanza si desidera il conteggio dei riferimenti senza la necessità di creare un'interfaccia con tutti i metodi e le proprietà definite in essa. Ci sono tre modi per farlo, e questi sono all'incirca nell'ordine in cui li consiglierei.

  1. Barry Kelly ha scritto un post su Smart Pointers. Utilizza Generics in Delphi 2009, ma sono abbastanza sicuro che potresti codificarlo con le versioni specifiche del tipo che stai utilizzando se non stai ancora utilizzando il 2009 (è una grande versione di BTW).

  2. Un altro modo che funziona con più versioni di Delphi e meno modifiche è il value type wrapper di Janez Atmapuri Makovsek. È un esempio implementato per TStringList, ma è possibile adattarlo a qualsiasi tipo.

  3. Il terzo modo è creare un puntatore interfacciato (simile a Smart Pointer di Barry, ma non così intelligente). Credo che ce ne sia uno in JCL, ma di sicuro non ricordo i dettagli. Fondamentalmente questa è un'interfaccia che accetta un riferimento TObject in fase di costruzione. Quindi quando il conteggio dei riferimenti raggiunge lo zero, chiama gratuitamente sull'oggetto che gli hai passato. Questo metodo funziona solo per le istanze di breve durata che non passano come parametri perché si separa il riferimento conteggiato di riferimento dal riferimento effettivamente utilizzato. Suggerirei invece uno degli altri due metodi, ma se preferisci questo metodo e vuoi maggiori informazioni fammelo sapere.

Quella è la cosa su Delphi, ci sono modi liberi di realizzare le cose. L'opzione numero 1 è la migliore secondo me - Ottieni Delphi 2009 e usa quel metodo se puoi.

Buona fortuna!

+0

Ho letto di nuovo la tua domanda e non so che nessuna di queste risposte funzioni poiché i tuoi elenchi accettano oggetti perché usano puntatori. Questi metodi utilizzano interfacce o record. Di nuovo, il passaggio a Delphi 2009 fornisce contenitori generici che funzionano con record, interfacce, oggetti o tipi nativi. –

+0

Mentre penso che tu abbia buoni punti nella tua risposta, nessuno dei tre modi aiuta a risolvere il problema dell'OP - nessuno di questi può essere usato al posto di un'istanza TObject, per eliminare la necessità di chiamare Free(). – mghie

+0

@JimMcKeeth: sto usando Delphi 2009, ma manca ancora un'implementazione di mappa hash generica e ragionevolmente efficiente, vero? – jpfollenius

1

Per aggiungere a ciò che è già stato detto, se si desidera memorizzare i riferimenti alle interfacce, invece di utilizzare un TList, utilizzare un TInterfaceList . Il conteggio ref funzionerà in modo coerente.

+0

Il problema è: ho bisogno di un'efficiente implementazione della mappa hash. Quello che sto usando (Barry Kelly lo ha pubblicato nel forum IIRC di codegear) usa i puntatori quindi non posso lavorare con le interfacce a meno che non disponga di un'implementazione di mappa hash che memorizza le interfacce. – jpfollenius

3

Se si desidera eliminare le chiamate gratuite su istanze TObject, è possibile che si desideri esaminare un garbage collector per Delphi nativo. Sono a conoscenza di 2 diversi netturbini e una tecnica di raccolta dei rifiuti, ciascuno con pro e contro.

Uno di quelli probabilmente funzionerà per te.

+0

+1 grazie! Li darò un'occhiata. – jpfollenius