![]() |
Delphi-Version: 10.4 Sydney
Automatische Referenzzählug bei Interfaces
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,
ich wollte alten Code umstellen und ein Interface implementieren anstatt überall im Code die Klassenimplementierung zu kennen. Die Umstellung klappte recht gut und alle Unit Tests laufen erfolgreich durch. Als ich dann aber die Speichercheck Option angeschaltet habe kam es bei einem Testfall zu nicht wieder freigegebenen Speicher. Benutze ich die Klassen entsteht kein Speicherproblem. Die Klasse implementiert "TInterfacedObject" und daher sollte sich ja Delphi um das Aufräumen des Speichers kümmern. Das klappt auch in fast allen Tests. Der Code ist ein Formelparser der dann die Ableitung bilden kann. Bei "3*r^2" kommt es allerdings zum Speicherleck, da hier die Ableitungsregeln für die Multiplikation und Potenzen angewendet werden. Für die Ableitung wird eine Formel-Instanz nach den Regeln erstellt. Multiplikation
Code:
Potenz
var newOp1:=TMultiplyTerm.create(fop1.getDerivative, fop2.copy);
var newOp2:=TMultiplyTerm.create(fop1.copy, fop2.getDerivative); var temp:=TAddTerm.create(newop1.simplify, newop2.simplify); result:=temp.simplify; {$IFDEF TERM_INTERFACE} newop1:=nil; newop2:=nil; temp:=nil; {$ELSE} newop1.Free; newop2.Free; temp.Free; {$ENDIF}
Code:
Laut meinem Verständnis ist ja das setzen auf NIL von Hand unnötig. Es hat in diesem Testfall auch keinen Effekt. var newop2:=TPowerTerm.create(getFirstOperand.copy, TConstantTerm.create(getSecondOperand.getValue(0)-1)); var temp:=TMultiplyTerm.create(getSecondOperand.copy, newop2.simplify); result:=temp.simplify; {$IFDEF TERM_INTERFACE} newop2:=nil; temp:=nil; {$ELSE} newop2.free; temp.Free; {$ENDIF} Simplify, Copy und getDerivative erstellen jeweils eine neue Instanz. Irgendwie scheint die Referenzählung bei der Komplexität durcheinander zu kommen. Hat jemand evtl. eine Idee? Ich habe auch ein TestTool erstellt welches bei jedem Create und Destroy eine Logausgabe erstellt. Vielen Dank im Vorraus Nick Bierwisch |
AW: Automatische Referenzzählug bei Interfaces
Drei grundsätzliche Punkte will ich schon mal nennen:
1) Ist newOp1 eine Interfacevariable? Davon gehe ich nicht aus. Du wirst sie als MyIntVar: IMyIntf deklarieren müssen. 2) Man sollte Interface-Parameter nie als ".Create" übergeben. Statt
Delphi-Quellcode:
also besser
MyProc(TMyIntf.Create);
Delphi-Quellcode:
Sonst kann (wird?) es Probleme mit der Referenzzählung geben.
var
tmpIntf: IMyIntf; ... tmpIntf := TMyIntf.Create; MyProc(tmpIntf); 3) Die ifdef-Stellen würde ich vermeiden. Ich habe jetzt kein Delphi zu Hand, finde das aber verwirrend. Ich würde eher eine Projektkopie erstellen und dort auf Interfaces umstellen. Deine Klasse(n) dürfen nirgends benutzt werden, außer einmalig das Create. Ansonsten IMMER nur Interfacevariablen verwenden. Das Create kann man in einer Factory verstecken, die direkt fertige Interfaces herausgibt. Nur sie muss dann (im Implementationsteil) die eigentlichen Klassen kennen. |
AW: Automatische Referenzzählug bei Interfaces
Liste der Anhänge anzeigen (Anzahl: 1)
Du sagst die Unit Tests laufen durch. Wo sind die? Ich dachte jetzt kommt ein Testprojekt, und keine Oberfläche zum Klicken. Vielleicht verstehen wir beide unter "Unit Tests" ja auch was anderes.
Die Speicherlecks kann man relativ einfach finden:
Als Beispiel habe ich das Ergebnis mal im Anhang mitgeliefert. |
AW: Automatische Referenzzählug bei Interfaces
Ohne mir den Quellcode jetzt genauer angesehen zu haben wären mein spontaner Verdacht zirkuläre Referenzen: Angenommen ein ITerm
Delphi-Quellcode:
verweist auf einen Term
a
Delphi-Quellcode:
und der verweist auch wieder auf
b
Delphi-Quellcode:
.
a
Angenommen in deinem Code interessiert sich mittlerweile niemand mehr für
Delphi-Quellcode:
oder
a
Delphi-Quellcode:
bekommen beide doch niemals ihren Referenzzähler auf Null und werden somit nie gelöscht.
b
Es gibt verschiedene Auswege hieraus, mehr oder weniger aufwändig. Garbage-Collection-Systeme haben solche Probleme nicht. |
AW: Automatische Referenzzählug bei Interfaces
Überall wo du mit inline variables arbeitest und sowas schreibst:
Delphi-Quellcode:
musst das das auflösen.
var newOp1:=TBlablaTerm.create(...);
Am Besten ganz klassisch mit Variablen-Block oben:
Delphi-Quellcode:
Dann klappt es auch mit dem automatischen Referenzzählen.
var
newOp1: ITerm; begin newOp1 := TBlablaTerm.create(...); Begründung: Wenn du
Delphi-Quellcode:
aufrufst, wird eine Instanz von TBlablaTerm erstellt und keine "Interface-Instanz" vom Typ ITerm.
var newOp1:=TBlablaTerm.create
|
AW: Automatische Referenzzählug bei Interfaces
Beispiele (Formatierung teilweise anders):
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
{$IFDEF TERM_INTERFACE} function createFormula: ITerm; var op1, op2, power, multOP1: ITerm; {$ELSE} function createFormula: TTerm; {$ENDIF} begin op1 := TVariableRTerm.create; op2 := TConstantTerm.create(2); power := TPowerTerm.create(op1, op2); multOP1 := TConstantTerm.create(3); result := TMultiplyTerm.create(multOP1, power); end; function getDerivativeAt(xvalue: double): double; var term, d: ITerm; begin term := createFormula; d := term.getderivative; Result := d.getValue(abs(xvalue)); {$IFDEF TERM_INTERFACE} // term:=nil; // d:=nil; {$ELSE} term.free; d.Free; {$ENDIF} end; ...
Delphi-Quellcode:
{$IFDEF TERM_INTERFACE}
function TMultiplyTerm.getDerivative:ITerm; var newOp1, newOp2, temp: ITerm; {$ELSE} function TMultiplyTerm.getDerivative:TTerm; {$ENDIF} begin newOp1:=TMultiplyTerm.create(fop1.getDerivative, fop2.copy); newOp2:=TMultiplyTerm.create(fop1.copy, fop2.getDerivative); temp:=TAddTerm.create(newop1.simplify, newop2.simplify); result:=temp.simplify; ...
Delphi-Quellcode:
{$IFDEF TERM_INTERFACE}
function TPowerTerm.getDerivative:ITerm; var newop2: ITerm; temp: ITerm; {$ELSE} function TPowerTerm.getDerivative:TTerm; {$ENDIF} begin if getSecondOperand.isConstant then begin newop2:=TPowerTerm.create(getFirstOperand.copy, TConstantTerm.create(getSecondOperand.getValue(0)-1)); temp:=TMultiplyTerm.create(getSecondOperand.copy, newop2.simplify); result:=temp.simplify; ... |
AW: Automatische Referenzzählug bei Interfaces
Das ist übrigens ein gutes Beispiel, weshalb inline Variablen (und dann noch mit Type Inference) oft keine gute Idee sind. Nur weil neue Delphiversionen das (leider) können, muss man es ja nicht überall nutzen...
|
AW: Automatische Referenzzählug bei Interfaces
Vielen Dank für die schnellen Antworten.
Das mit den Inline Variablen war mir so nicht bewußt. Ich werde es zukünftig weniger nutzen. In diesem Beispiel ist es egal aber ich nutze es gerne innerhalb von Schleifen oder Blöcken damit die Variable nur lokal nutzbar ist und nicht woanders nochmal genutzt werden kann. Ich bin davon ausgegangen das Delphi dann den Speicher beim Verlassen des Blocks oder der Schleife automatisch freigibt. Das würde ja die "try finally free" Konstrukte sparen. |
AW: Automatische Referenzzählug bei Interfaces
@Günther
Ich hatte nur den minimalen Code für den Fehlerfall hochgeladen. Ansonsten hätte ich noch die ganze Umgebung inklusive Parser usw. auch liefern müssen. Das waäre dann viel Code gewesen der ja für die Lösung nicht notwendig war. Den vollen Speichermanager werde ich mir herunterladen. |
AW: Automatische Referenzzählug bei Interfaces
Zitat:
Du hast völlig Recht wenn du meinst dass eine Variable vom Typ "Interface" automatisch freigegeben wird wenn ihr Referenzzähler auf Null geht. Bei einer lokalen Variable ist das üblicherweise der Fall wenn der Gültigkeitsbereich (for-Schleife, begin/end-Block, ...) zu Ende ist. Nicht der Fall ist das natürlich wenn die Referenz noch irgendwo anders hin weitergegeben und dort gespeichert wird. Bei den Inline-Variablen ist hier ein Sonderfall, der eigentlich keiner ist: Angenommen du hast eine Klasse
Delphi-Quellcode:
.
TTerm = class(TInterfacedObject, ITerm)
Wenn du eine lokale Variable anlegst dann kann die bspw. vom Typ TTerm oder ITerm sein. Wenn du schon ein Interface hast, dann solltest du das auch benutzen und sparst dir, wenn du eine neue Instanz anlegst, auch gleich das
Delphi-Quellcode:
mit. So wie man es schon immer auch von Strings oder dynamischen Arrays kennt...
try..finally
Jetzt der Stolperstein: Wenn du bspw. sagst
Delphi-Quellcode:
, dann fahr doch mal später mit der Maus über meinTerm und schau was der Computer meint von welchem Typ das ist. Erstaunt? Woher soll der Computer auch wissen dass für dich ein
var meinTerm := TTerm.Create()
Delphi-Quellcode:
besser wäre? Du hast ja ganz explizit
ITerm
Delphi-Quellcode:
gesagt.
TTerm.Create()
Und Variablen vom Typ TTerm werden natürlich nicht beim Verlassen des Gültigkeitsbereichs freigegeben, das war ja schon immer so. Wir müssen jetzt also nur einen Weg finden explizit zu sagen dass die Variable bitte vom Typ Interface ist. Es gibt zwei Möglichkeiten mit denen du den Inline-Variablen nicht abschwören musst, die sind nämlich gar nicht böse.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:56 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz