![]() |
Objekt in Array kopieren und anschließend freigeben
Hallo.
Ich habe in einem Projekt Records in Klassen umgewandelt, da ich von diesen nun ableiten kann. Seit dem erhalte ich eine AV. Hier erstmal der vereinfachte Code zur Reproduktion:
Delphi-Quellcode:
Submission ist ein Objekt vom Typ TSubmission und wird 10 Mal dynamisch erstellt und mit Werten belegt. Anschließend möchte ich die Submission in einen SubmissionArray hängen (siehe AddToArray).
type
// Anmerkung: War mal ein record, andere Klassen leiten aber jetzt hiervon ab TSubmission = class Tags: string; Rating: string; Score: integer; PictureNumber: integer; MD5: string; end; TSubmissionArray = array of TSubmission; procedure AddToArray(var SubmissionArray: TSubmissionArray; const Value: TSubmission); var l: integer; begin l := Length(SubmissionArray); SetLength(SubmissionArray, l + 1); SubmissionArray[l] := Value; end; function Collect: TSubmissionArray; var i: integer; Submission: TSubmission; tmp: TSubmissionArray; begin for i := 0 to 10 do begin Submission := TSubmission.Create; try Submission.MD5 := 'test'+IntToStr(i); AddToArray(tmp, Submission); finally Submission.Free; // PROBLEM! end; end; showmessage(tmp[2].MD5); // Leer, durch Free() result := tmp; end; procedure TForm1.Button1Click(Sender: TObject); var x: TSubmissionArray; begin x := Collect; showmessage(x[2].MD5); // Leer, durch Free(); // In einem anderen Projekt sogar eine AccessViolation bei ntdll.dll end; Anschließend gebe ich Submission frei. Und dies verursacht bei mir eine AV bei allen zukünftigen Zugriffen auf die Array-Elemente und deren Eigenschaften. Kommentiere ich die Zeile "Submission.Free" aus, funktioniert alles. Dies erachte ich jedoch als ein Problem, da der Speicher nicht freigegeben wird. Was passiert hier? Wieso wird in der Funktion AddToArray() keine Kopie der TSubmission-Instanz gemacht, die in den Array wandert? Wieso gehen die Array-Elemente kaputt, wenn ich das Originalstück freigebe? Bei Strings, die als Variable in eine Funktion wandern, kenne ich die Routine UniqueString(). Hier muss ich mein TSubmission irgendwie auch "einzigartig" machen, oder? Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Im Array legst du ja nur Referenzen auf die Objekte ab. Wenn du diese dann freigibst, zeigen diese ins Leere
|
Re: Objekt in Array kopieren und anschließend freigeben
Soll ich dann auf den Ressourcenschutzblock verzichten? Wer kümmert sich dann um die Speicherfreigabe?
|
Re: Objekt in Array kopieren und anschließend freigeben
Du selber mit dem .Free()
|
Re: Objekt in Array kopieren und anschließend freigeben
Meinst du etwa so?
1. Ich entferne den Ressourcenschutzblock (Free) in der Funktion "Collect". 2. Ich gebe alle Arrayelemente in meinem Button OnClick nacheinander frei
Delphi-Quellcode:
Ist das sinnvoll? Eigentlich habe ich mir angewöhnt, überall einen Ressourcenschutzblock sofort einzubauen, nachdem man etwas mit Create() erstellt hat.
procedure TForm1.Button1Click(Sender: TObject);
var x: TSubmissionArray; i: integer; begin x := Collect; showmessage(x[2].MD5); // Array und seine Elemente werden ab hier nicht mehr gebraucht for i := 0 to high(x) do begin x[i].Free; end; end; Die dieser Lösung jetzt dürfte man das Freigeben in der For-Schleife niemals vergessen, wenn man mit einem TSubmissionArray arbeitet, oder? Gedankengang A: Das TSubmissionArray "x" ist als Variable innerhalb von Button1Click deklartiert. Muss man die einzelnen Elemente überhaupt explizit freigeben? Oder wird dies automatisch gemacht, da der Array automatisch freigegeben wird (Ich glaube nicht, da er nur Referenzen enthält). Gedankengang B: Wäre es nicht besser, wenn ich in AddToArray() das TSubmission-Objekt irgendwie verdoppeln (einzigartig machen) könnte? Dann könnte ich meinen Ressourcenschutzblock behalten und müsste mich nicht um das Freigeben in einer For-Schleife kümmern. Nur wie macht man das? Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Wenn du diese in eine Objektliste steckst, kannst du auf das manuelle Freigeben verzichten, bei einem Array nicht
|
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Wenn ein Array immer eine separate Freigabe benötigt, dann brauche ich ja den Ressourcenschutzblock in meiner Prozedur, die den Array mit Elementen befüllt, nicht mehr. Ich habe jetzt das Freigeben der Arrayelemente ("Freigeben des Arrays") noch ausgelagert:
Delphi-Quellcode:
Diese Prozedur führe ich nun aus, wenn ich den Array nicht mehr brauche. Zu Memory-Leaks kommt es nicht.
procedure FreeSubmissionArray(SubmissionArray: TSubmissionArray);
var i: integer; begin for i := 0 to High(SubmissionArray) do begin SubmissionArray[i].Free; end; end; Ist das jetzt eine Variante, wie sie auch professionell gemacht werden würde, oder ist das, was ich hier mache, in irgend einer Weise ungünstig? Es ist eben irgendwie für mich von Nachteil, da ich nun bei den TSubmissionArrays niemals FreeSubmissionArray() vergessen darf. Ich hatte auch noch mal probiert, die Funktion AddToArray so zu modifizieren, dass das Objekt vor dem Hinzufügen zum Array kopiert wird. Ist leider fehlgeschlagen:
Delphi-Quellcode:
Entschuldige bitte mein Unwissen, aber was ist eine "Objektliste"? Diesen Ausdruck habe ich noch nicht gehört. Auch Google sagt mir jetzt nicht wirklich eine Definition von dem Begriff.
procedure AddToArray(var SubmissionArray: TSubmissionArray; const Value: TSubmission);
var l: integer; begin l := Length(SubmissionArray); SetLength(SubmissionArray, l + 1); SubmissionArray[l] := TSubmission(Value.NewInstance); // Kopieren? Eher nicht... end; Darf ich eine kleine Frage am Rande stellen? Eine überladene Funktion von AddToArray arbeitet mit Strings:
Delphi-Quellcode:
Muss ich hier mit UniqueString() arbeiten, oder wird hier der String tatsächlich in den Array kopiert? Wenn nicht, dann würde sich doch der Array ändern, wenn ich den String, den ich AddToArray() gegeben habe, ändere, oder? :gruebel:
type
TStringArray: array of String; procedure AddToArray(var StringArray: TStringArray; const Value: string); var l: integer; begin l := Length(StringArray); SetLength(StringArray, l + 1); StringArray[l] := Value; end; Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Zitat:
Zitat:
Dein Array ist wieder nur so eine halbherzige Umsetzung des OOP Gedankens. |
Re: Objekt in Array kopieren und anschließend freigeben
Zitat:
![]() |
Re: Objekt in Array kopieren und anschließend freigeben
Zitat:
Zitat:
Zitat:
![]() |
Re: Objekt in Array kopieren und anschließend freigeben
Für solche Sachen enthält die TObjectList schließlich "OwnsObjects". In weiser Voraussicht haben sie das standardmäßig schon auf true gesetzt. Ist schon recht interessant, was man damit alles zusammenbauen kann. Und kaum einer weiß es.
|
Re: Objekt in Array kopieren und anschließend freigeben
Zitat:
|
Re: Objekt in Array kopieren und anschließend freigeben
Um mal auf deine Originalfragen noch einzugehen:
Zitat:
Zitat:
Zitat:
Prinzipiell ist es aber auch völlig richtig TObjectList einzusetzen. Ein Array ist für das was du dort vor hast recht ungeeignet. |
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Danke für eure Antworten. Ich habe jetzt den Array entfernt und durch eine abgeleitete Objektliste ersetzt. Jetzt erhalte ich eine AV, die ich absolut nicht verstehe. Wenn ich mit die Objektliste x befülle und freigebe, funktioniert alles. Gebe ich die Objektliste x in eine Funktion, die eine zweite Liste y erstellt, kommt eine AV bei Freigabe von x:
Delphi-Quellcode:
Zur schnellen Rekonstruktion noch folgender Zusatzcode mit Deklarationen:
function MachWas(Input: TSubmissionObjectList): TSubmissionObjectList;
var i: integer; begin result := TSubmissionObjectList.Create; for i := 0 to Input.Count - 1 do begin if odd(i) then begin result.Add(Input.Items[i]); end; end; end; procedure TForm1.FormCreate(Sender: TObject); var x, y: TSubmissionObjectList; sub: TSubmission; i: integer; begin x := TSubmissionObjectList.Create; try for i := 0 to 100 do begin sub := TSubmission.Create; sub.MD5 := 'Test'+IntToStr(i); x.Add(sub); end; // Ist dieser Abschnitt auskommentiert, keine AV bei x.Free; y := MachWas(x); try finally y.Free; end; finally x.Free; // AccessViolation durch y.Free! end; end;
Delphi-Quellcode:
Ich befürchte, das hat wieder was damit zu tun, da Objekte nur referenziert und nicht kopiert werden. Ich weiß irgendwie nicht mehr weiter :(
type
TSubmission = class(TObject) Tags: string; Rating: string; Score: integer; PictureNumber: integer; MD5: string; end; TSubmissionObjectList = class(TObjectList) protected function getItem(Index: Integer): TSubmission; virtual; procedure setItem(Index: Integer; Objekt: TSubmission); virtual; public function Add(Objekt: TSubmission): Integer; virtual; function Remove(Objekt: TSubmission): Integer; virtual; function IndexOf(Objekt: TSubmission): Integer; virtual; procedure Insert(Index: Integer; Objekt: TSubmission); virtual; function First: TSubmission; virtual; function Last: TSubmission; virtual; property Items[index: Integer]: TSubmission read getItem write setItem; default; end; function TSubmissionObjectList.getItem(Index: Integer): TSubmission; begin Result := TSubmission(inherited Items[Index]); end; procedure TSubmissionObjectList.setItem(Index: Integer; Objekt: TSubmission); begin inherited Items[Index] := Objekt; end; function TSubmissionObjectList.Add(Objekt: TSubmission): Integer; begin Result := inherited Add(Objekt); end; function TSubmissionObjectList.First: TSubmission; begin Result := TSubmission(inherited First()); end; function TSubmissionObjectList.IndexOf(Objekt: TSubmission): Integer; begin Result := inherited IndexOf(Objekt); end; procedure TSubmissionObjectList.Insert(Index: Integer; Objekt: TSubmission); begin inherited Insert(Index, Objekt); end; function TSubmissionObjectList.Last: TSubmission; begin Result := TSubmission(inherited Last()); end; function TSubmissionObjectList.Remove(Objekt: TSubmission): Integer; begin Result := inherited Remove(Objekt); end; Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Deine Vermutung ist richtig. Dadurch, dass Du die Objekte (besser gesagt, deren Referenzen) in die 2. Liste packst und diese dann freigibst, zeigen die entsprechenden Objektreferenzen der 1. Liste dann ins Leere.
|
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Und was macht man dagegen? Ich kann weder auf y.Free, noch auf x.Free verzichten, da ich sonst MemoryLeaks habe. Vielleicht könnte man TSubmissionObjectList.Add() frisieren, sodass es die Objekte vorher dupliziert und dann das Duplikat hinzufügt? Die resultierende Liste y sollte eigentlich von Liste x unabhängig abhängig sein. Wie mache ich das am geschicktesten? Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Du könntest in der Liste, die MachWas zurückgibt, OwnsObjects auf False setzen. Dann wird es allerdings wieder problematisch, falls du y neue Objekte hinzufügst.
Am besten ist wohl, TSubmission von TPersistent abzuleiten und Assign zu überschreiben. In MachWas könntest du dann die Objekte klonen. Mir fällt gerade noch eine weitere Lösung ein: Du könntest auch ein ISubmission-Interface deklarieren und die Submissions in einer TInterfaceList speichern. Die automatische Referenzzählung würde dich dann von der Pflicht befreien, die Submissions manuell freizugeben. |
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Ich würde es vorziehen, die TSubmissionObjectList zu modifizieren, sodass sie neue Objekte dupliziert und man somit eine unabhängige Liste erzeugen kann. Dann müsste ich mich nur 1 Mal direkt in der TSubmissionObjectList um diese Angelegenheit kümmern, anstelle jede Funktion umzuschreiben, die mit TSubmissionObjectList arbeiten. Ist doch korrekt so, oder? Ich habe jetzt folgendermaßen geändert:
Delphi-Quellcode:
Fehlermeldung: 'TSubmission kann nicht zu TSubmission zugewiesen werden'.
type
TSubmission = class(TPersistent) public Tags: string; Rating: string; Score: integer; PictureNumber: integer; MD5: string; procedure Assign(Source: TPersistent); override; end; procedure TSubmission.Assign(Source: TPersistent); begin inherited; // Was soll ich hier machen? end; function TSubmissionObjectList.Add(Objekt: TSubmission): Integer; var Duplikat: TSubmission; begin Duplikat:= TSubmission.Create; Duplikat.Assign(Objekt); Result := inherited Add(Duplikat); end; Du schriebst, dass ich Assign() überschreiben müsste. Was muss ich denn genau am Code ändern, sodass Assign an mein TSubmission angepasst wird? Der Sourcecode von TPersistent.Assign() hat mir irgendwie nicht die geistige Erleuchtung gebracht. :coder2: -- Zu deinem Nachtrag: Ich werde das mit dem Interface gleich mal in einem zweiten Testprojekt ausprobieren. Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Zitat:
|
Re: Objekt in Array kopieren und anschließend freigeben
Die klassische Implementation von Assign sieht so aus:
Delphi-Quellcode:
Du musst also einfach die Felder von Hand kopieren. Lediglich bei Feldern, die selbst Objekte beinhalten, solltest du überlegen, ob du nicht wiederum Assign verwendest, falls die Klasse es implementiert.
procedure TMeinObjekt.Assign(Source: TPersistent);
begin if Source is TMeinObjekt then begin Feld1 := TMeinObjekt(Source).Feld1; Feld2 := TMeinObjekt(Source).Feld2; Feld3 := TMeinObjekt(Source).Feld3; Feld4 := TMeinObjekt(Source).Feld4; end else inherited; end; Bezüglich Interfaces: Dann musst du Properties mit Get- und Set-Methoden verwenden. Das ist allerdings etwas Schreibarbeit. |
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Vielen Dank für die Hinweise. Ich habe meine Objektliste bei Add() und Insert() so modifiziert, sodass sie die Objekte dupliziert anstelle nur zu referenzieren. Ich muss mich jetzt beim Umgang mit Add() also nicht mehr explizit um die Duplizierung kümmern. Außerdem kann ich meine Objekte dann sofort freigeben, was bei den Ressourcenschutzblöcken für Übersichtlichkeit sorgt. Ich hoffe, dass mein Code nun professioneller in Sachen OOP geworden ist.
Delphi-Quellcode:
Gruß
type
TSubmission = class(TPersistent) public Tags: string; Rating: string; Score: integer; PictureNumber: integer; MD5: string; procedure Assign(Source: TPersistent); override; end; TSubmissionCollection = class(TObjectList) protected function getItem(Index: Integer): TSubmission; virtual; procedure setItem(Index: Integer; Objekt: TSubmission); virtual; public function Add(Objekt: TSubmission): Integer; virtual; function Remove(Objekt: TSubmission): Integer; virtual; function IndexOf(Objekt: TSubmission): Integer; virtual; procedure Insert(Index: Integer; Objekt: TSubmission); virtual; function First: TSubmission; virtual; function Last: TSubmission; virtual; property Items[index: Integer]: TSubmission read getItem write setItem; default; end; { TSubmission } procedure TSubmission.Assign(Source: TPersistent); begin if Source is TSubmission then begin Tags := TSubmission(Source).Tags; Rating := TSubmission(Source).Rating; Score := TSubmission(Source).Score; PictureNumber := TSubmission(Source).PictureNumber; MD5 := TSubmission(Source).MD5; end else inherited; end; { TSubmissionCollection } function TSubmissionCollection.getItem(Index: Integer): TSubmission; begin Result := TSubmission(inherited Items[Index]); end; procedure TSubmissionCollection.setItem(Index: Integer; Objekt: TSubmission); begin inherited Items[Index] := Objekt; end; function TSubmissionCollection.Add(Objekt: TSubmission): Integer; var duplikat: TSubmission; begin duplikat := TSubmission.Create; duplikat.Assign(Objekt); Result := inherited Add(duplikat); end; function TSubmissionCollection.First: TSubmission; begin Result := TSubmission(inherited First()); end; function TSubmissionCollection.IndexOf(Objekt: TSubmission): Integer; begin Result := inherited IndexOf(Objekt); end; procedure TSubmissionCollection.Insert(Index: Integer; Objekt: TSubmission); var duplikat: TSubmission; begin duplikat := TSubmission.Create; duplikat.Assign(Objekt); inherited Insert(Index, duplikat); end; function TSubmissionCollection.Last: TSubmission; begin Result := TSubmission(inherited Last()); end; function TSubmissionCollection.Remove(Objekt: TSubmission): Integer; begin Result := inherited Remove(Objekt); end; { Anwendungsbeispiel } function Filtere(Input: TSubmissionCollection): TSubmissionCollection; var i: integer; begin result := TSubmissionCollection.Create; for i := 0 to Input.Count - 1 do begin if odd(i) then begin result.Add(Input.Items[i]); end; end; end; procedure TForm1.FormCreate(Sender: TObject); var x, y: TSubmissionCollection; sub: TSubmission; i: integer; begin x := TSubmissionCollection.Create; try for i := 0 to 100 do begin sub := TSubmission.Create; try sub.MD5 := 'Test'+IntToStr(i); x.Add(sub); finally sub.Free; end; end; y := Filtere(x); try finally y.Free; end; finally x.Free; end; end; blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Mal eine andere Frage: Warum brauchst du eine Kopie einer Liste, wenn du die Ausgangsliste direkt nach dem Kopieren entfernen willst? Wäre es nicht geschickter die bestehende Liste weiter zu verwenden?
|
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Bei meinem verwendeten Code geht die Liste mit allen Submissions in 2 Filterfunktionen, die wiederum 2 Listen erstellen. Die Originalliste wird auch noch verwendet, nachdem sie in 2 Unterlisten aufgeteilt wurde. Aber mal zu einem anderen Problem - Bei meiner Arbeit mit den TPersistent-Nachkommen und dem Assign()-Befehl habe ich wieder einen Nachteil entdeckt, der mir etwas zu wider ist: Ich bin ja von den records+arrays weggegangen, da man einen record nicht ableiten kann. Es sei jetzt eine neue Klasse von TSubmission abgeleitet:
Delphi-Quellcode:
Dann werden alle Elemente von TSubmission mitgenommen. Das ist sehr gut. Aber bei der TExtendedSubmission.Assign() Methode geht das ganze ja wieder zu bruch.
type
TExtendedSubmission = class(TSubmission) public Extension: string; DateTime: string; Author: string; procedure Assign(Source: TPersistent); override; end;
Delphi-Quellcode:
In dieser Assign()-Methode MUSS ich alle Elemente der TSubmission (Tags, Rating, Score, PictureNumber und MD5) explizit in den Code mit einbinden. Und ich sehe keine Lösung dafür.
{ TExtendedSubmission }
procedure TExtendedSubmission.Assign(Source: TPersistent); begin if Source is TExtendedSubmission then begin { Hartcoding von TSubmission } // TSubmission würde durch diesen Code zur Unveränderlichkeit gezwungen werden, // da bei einer Veränderung alle Nachkommen ungültig werden würden Tags := TExtendedSubmission(Source).Tags; Rating := TExtendedSubmission(Source).Rating; Score := TExtendedSubmission(Source).Score; PictureNumber := TExtendedSubmission(Source).PictureNumber; MD5 := TExtendedSubmission(Source).MD5; { Nun die neuen Methoden der eigenen Klasse } Extension := TExtendedSubmission(Source).Extension; DateTime := TExtendedSubmission(Source).DateTime; Author := TExtendedSubmission(Source).Author; end else inherited; end; Nehmen wir mal an, ich würde TSubmission verändern/erweitern. TExtendedSubmission würde sich automatisch mit erweitern. Das ist der große Vorteil gegenüber den records, die ja leichter zu handhaben sind. Aber die Methode TExtendedSubmission.Assign() bleibt weiterhin unaktuell. Sie muss explizit angepasst werden, wenn ich TSubmission verändere. Allgemein müsste ich also ALLE Nachkommen in der Assign()-Methode separat umschreiben, wenn ich etwas am Grundtyp TSubmission ändere - sollte die OOP das nicht irgendwie verhindern? // Ich habe bezüglich der Problematik das Thema nochmal als offene Frage gesetzt. Gruß blackdrake |
Re: Objekt in Array kopieren und anschließend freigeben
Nimm das Inherited nicht in den Else-Zweig. In TSubmission.Assign ergibt die is-Abfrage ja immer noch True, sodass die alten Felder auch kopiert werden.
|
Re: Objekt in Array kopieren und anschließend freigeben
Hallo.
Vielen Dank für deine Antwort! Jetzt habe ich genau das, was ich wollte und die Klasse TSubmission kann nun beliebig verändert werden, ohne dass die Nachkommen darunter "leiden" :-D
Delphi-Quellcode:
Folgender Testcode erzeugt auch das gewünschte Ergebnis:
{ TSubmission }
procedure TSubmission.Assign(Source: TPersistent); begin if Source is TSubmission then begin Tags := TSubmission(Source).Tags; Rating := TSubmission(Source).Rating; Score := TSubmission(Source).Score; PictureNumber := TSubmission(Source).PictureNumber; MD5 := TSubmission(Source).MD5; end else inherited; end; { TExtendedSubmission } procedure TExtendedSubmission.Assign(Source: TPersistent); begin if Source is TExtendedSubmission then begin Extension := TExtendedSubmission(Source).Extension; DateTime := TExtendedSubmission(Source).DateTime; Author := TExtendedSubmission(Source).Author; end; inherited; end;
Delphi-Quellcode:
Gruß
procedure TMainForm.Button1Click(Sender: TObject);
var x, y: TExtendedSubmission; begin x := TExtendedSubmission.Create; try x.MD5 := 'Test'; y := TExtendedSubmission.Create; try y.Assign(x); showmessage(y.MD5); // = "Test" finally y.Free; end; finally x.Free end; end; blackdrake |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:37 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