2012-10-12 6 views
9

Hier habe ich eine schwierige Situation, denke ich. Ich muss in der Lage sein, ein Objekt zu befreien, das ein Feld einer Aufzeichnung ist. Ich würde normalerweise den Bereinigungscode im Destruktor schreiben, wenn es eine Klasse wäre. Aber da Datensatztypen keinen "Destruktor" einführen können, wie wäre es möglich, TObject (Field) .Free;?Wie man ein Objekt, das in einem Datensatz ist, freigibt?

Es werden zwei Arten von Nutzungs sein Ich sage voraus:

  1. den Datensatz durch einen neuen zu ersetzen.

    Ich denke, diese Verwendung wäre einfach zu implementieren. Da Datensätze Werttypen sind und daher bei der Zuweisung kopiert werden, kann ich den Zuweisungsoperator überlasten und die Objekte freigeben, die dem alten Datensatz gehören.

    (Edit: Zuordnung Überlastung war nicht in der Lage Das ist eine neue Infos zu mir ...)

  2. Verlassen des Umfangs, wo Record-Variable definiert.

    Ich kann an eine private Methode denken, die die Objekte freigibt, und diese Methode könnte bei der Bereichsanregung manuell aufgerufen werden. ABER, hier ist die gleiche Frage: Wie man es mehr macht recordly? Dieses Verhalten Art fühlt sich wie eine Klasse ...

Hier ist ein Beispiel (und offensichtlich nicht die beabsichtigte Verwendung):

TProperties = record 
    ... some other spesific typed fields: Integers, pointers etc.. 
    FBaseData: Pointer; 

    FAdditionalData: TList<Pointer>; 
    //FAdditionalData: array of Pointer; this was the first intended definition 
end; 

Angenommen,

FAdditionalData:=TList<Pointer>.Crete; 

genannt im Datensatzkonstruktor oder manuell im Datensatzvariablenbereich, indem auf das Feld öffentlich zugegriffen wird wie

procedure TFormX.ButtonXClick(Sender: TObject); 
var 
    rec: TProperties; 
begin 
    //rec:=TProperties.Create(with some parameters); 

    rec.FAdditionalData:=TList<Pointer>.Create; 

    //do some work with rec 
end; 

Nach dem Button Umfang der rec ist nicht mehr, aber ein TList noch hält seine Existenz verlässt, die Speicherlecks verursacht ...

+1

Datensatzzuordnung kann nicht überladen werden. – kludg

+0

Ich war mir dessen nicht bewusst (nie zuvor gebraucht), aber ich habe es jetzt gelernt :) Ja, es konnte nicht überlastet werden ... –

Antwort

10

Wenn alles, was Sie in der Aufzeichnung haben eine Objektreferenz ist, dann können Sie den Compiler nicht dazu bringen, Ihnen zu helfen. Sie sind allein verantwortlich für die Lebensdauer dieses Objekts. Sie können den Zuweisungsoperator nicht überladen, und Sie erhalten keine Benachrichtigung über den Abschluss des Umfangs.

Sie können jedoch eine Guard-Schnittstelle hinzufügen, die die Lebensdauer des Objekts verwaltet.

TMyRecord = record 
    obj: TMyObject; 
    guard: IInterface; 
end; 

Sie müssen sicherstellen, dass TMyObject seine Lebensdauer durch Referenzzählung verwaltet. Zum Beispiel durch Ableiten von TInterfacedObject.

Wenn Sie den Datensatz initialisieren Sie dies tun:

rec.obj := TMyObject.Create; 
rec.guard := rec.obj; 

An diesem Punkt das guard Feld des Datensatzes wird nun Ihre Aufgabe Lebzeiten verwalten.

In der Tat, wenn Sie diese Idee weiter vorantreiben möchten, können Sie eine dedizierte Klasse erstellen, um die Lebensdauer von Objekten zu schützen. Das zwingt Sie nicht mehr, IInterface in Ihrer Klasse zu implementieren. Es gibt viele Beispiele im Internet, die diese Technik veranschaulichen. Zum Beispiel biete ich Jarrod Hollingworths Artikel mit dem Titel Smart Pointers und Barry Kellys Titel Reference-counted pointers, revisited an. Es gibt viel mehr da draußen. Es ist ein alter Trick!

Beachten Sie jedoch, dass das, was Sie hier haben, eine seltsame Mischung aus Werttyp und Referenztyp ist. Auf den ersten Blick sind Datensätze Werttypen. Dieser fungiert jedoch als Referenztyp. Wenn Sie im Datensatz andere Felder haben, die Werttypen sind, wäre das noch verwirrender. Sie müssen sich dieses Problems bewusst sein, wenn Sie mit einem solchen Datensatz arbeiten.

Auf den ersten Blick, ohne mehr über Ihr Design zu wissen, würde ich geneigt sein, Sie darauf hinzuweisen, Objektverweise nicht in Aufzeichnungen aufzunehmen. Sie passen besser in Referenztypen, d. H. Klassen.

+7

Ich würde vorschlagen, zuerst mit dem letzten Absatz zu führen. Das Einfügen von Klassen in Datensätze ist ein schneller Weg zum Buggy-Code, wenn man mit dem Lifetime-Management nicht sehr, sehr vorsichtig ist. – afrazier

+0

Sehr informative Antwort, danke Herr Heffernan .. Ich werde diese Wege ausprobieren. Ich muss die Frage aktualisieren, um klarer mit dem Design zu sein. Die Objektfelder sind ** TList ** s, aber es scheint so, als würde ich es zurückdrehen auf das, was es an erster Stelle war: ** array of Pointer ** –

+1

Die Verwendung von dynamischem Array löst das Life Time Management. Sie könnten das ausgezeichnete 'TDynArray' von Synopse in Erwägung ziehen, um die Verwendung dynamischer Arrays zu vereinfachen. –

3

Ich erinnere mich, dass jemand eine Klasse namens TLifetimeWatcher erstellt. Im Grunde ist es wie folgt aussieht:

TLifetimeWatcher = class(TInterfacedObject) 
private 
    fInstance: TObject; 
    fProc: TProc; 
public 
    constructor Create(instance: TObject); overload; 
    constructor Create(instance: TObject; proc: TProc); overload; 
    destructor Destroy; override; 
end; 

// Die (Bereinigung) proc wird im destructor ausgeführt werden, wenn zugeordnet, andernfalls wird die Instanz durch Aufruf der Methode Free befreit werden.

+0

Dies ist der grundlegende Ansatz in meiner Antwort skizziert. Ohne die Implementierung ist es nicht sehr nützlich. –

+2

"Jemand" ist Barry Kelly - http://blog.barrkel.com/2008/09/smart-pointers-in-delphi.html – kludg