![]() |
Intelligente Objekte - automatische Freigabe von Referenzen
Hiermit möchte ich ein Konzept zur automatischen Freigabe von Referenzen auf nicht mehr vorhandene Objekte zur Diskussion stellen.
Über das automatische Rücksetzen von Referenzen wurde schon viel diskutiert. Einer der Threads zu diesem Thema ist stahli's ![]() Jedem ist (sollte) bekannt (sein), daß nach
Delphi-Quellcode:
var
o1, o2: TObject; begin [...] o1:=TObject.Create; o2:=o1; o1.Free; [...] end; Listing 1
Delphi-Quellcode:
var
o1, o2: TObject; begin [...] o1:=TObject.Create; o2:=o1; FreeAndNil(o1); [...] end; Listing 2 Obwohl es zu diesem Thema meistens negative Meinungen gibt (braucht man nicht, Blödsinn, zeugt von einem schlechten Design, ...), existieren Fälle, in denen das automatische Setzen von ungültigen Referenzen auf nil von entscheidender Bedeutung ist. Dazu ein praktisches Beispiel mit dem ![]()
Delphi-Quellcode:
TForm1 = class([...])
[...] private FMyMarker: TMarker; //Stelle 1 [...] end; procedure TForm1.Button1Click(Sender: TObject); begin with Script do begin FMyMarker:=New(Google.Maps.Marker); //Stelle 2 FMyMarker.Position:=New(Google.Maps.Point(10,20)); FMyMarker.Map:=Maps[0]; end; end; Listing 3 Werden jetzt aber massenhaft neue Marker angelegt, steigt der Speicherverbrauch duch die Punkte, obwohl sie eigentlich gar nicht mehr benötigt werden. Abhilfe würde eine "ordentlichere" Programmierung im Delphi-Stil schaffen:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var Point: TPoint; begin with Script do begin Point:=New(Google.Maps.Point(10,20)); try FMyMarker:=New(Google.Maps.Marker); FMyMarker.Position:=Point; FMyMarker.Map:=Maps[0]; finally Point.Free; end; end; end; Listing 4
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var Point: IPoint; begin with Script do begin Point:=New(Google.Maps.Point(10,20)); FMyMarker:=New(Google.Maps.Marker); FMyMarker.Position:=Point; FMyMarker.Map:=Maps[0]; end; end; Listing 5 Das Marker-Objekt würde so aber - wenn es nicht in FMyMarker (vom Typ IMarker) referenziert würde - sofort nach Beendigung der Methode Button1Click wieder freigegeben und so von der Karte verschwinden. Um da zu vermeiden, muß die Liste TScript.Markers Interfaces verwenden. Dadurch ergibt sich aber eine äußerst ungünstige Situation: Eine Zuweisung von nil zu FMyMarker bewirkt augenscheinlich gar nichts und erst das zusätzliche Löschen mit TScript.Markers.Delete(...) oder TScript.Markers.Remove(...) gibt den Marker tatsächlich frei:
Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject);
begin with Script do begin Markers.Remove(FMyMarker); FMyMarker:=nil; end; end; Listing 6 Wäre es nicht bedeutend einfacher - zusätzlich zu den Möglichkeiten, die die Referenzzählung über ein Interface bietet -, ein Objekt explizit freigeben zu können - unabhängig davon, wieviele Referenzen noch bestehen -, als dessen Folge das Objekt aktiv alle Verweise auf nil setzt und sich in allen Listen abmeldet?
Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject);
begin FMyMarker.Free; end; Listing 7 Anders herum entfernt
Delphi-Quellcode:
procedure TForm1.Button2Click(Sender: TObject);
begin Script.Markers[...].Free; //oder Script.Markets.Delete(...); //oder Script.Markers.Remove(...); end; Listing 8 Das wäre ein erheblicher Fortschritt, würde die Vorteile der Verwendung von reinen Objekten mit denen von Interfaces verbinden und die Möglichkeiten sogar noch erweitern. Über derartige intelligente Zeiger und Objekte wurde schon einiges geschrieben - allerdings setzen alle mir bekannten Lösungen für Delphi auf die Verwendung von Generics und/oder anonymen Methoden und schließen damit ältere Compiler aus. Das Delphi Framework für Google Maps soll aber auch weiterhin ab Delphi 5 verwendbar sein. Deshalb wurde folgendes Interface entworfen:
Delphi-Quellcode:
type
INotify = interface(IInterface) ['{D37C5177-D900-4D99-A97B-A341865B258D}'] procedure AddRef(const Ref); procedure AddNotify(const Notify: INotify); procedure Free; procedure FreeNotify(const Notify: INotify); procedure RemoveRef(const Ref); procedure RemoveNotify(const Notify: INotify); function _ReleaseSave: Integer; function GetObject: TObject; function GetRefCount: Integer; function IsDestroying: Boolean; property RefCount: Integer read GetRefCount; end; Listing 9
Wird das Objekt, das INotify implementiert, freigegeben, informiert es alle angemeldeten Schnittstellen über FreeNotify und setzt alle Speicherreferenzen auf nil. Die Funktion GetObject unterstützt ältere Compiler, die noch keinen Interface-to-Object-Cast besitzen. IsDestroying liefert true, sobald sich das Objekt hinter dem Interface in der Methode Destroy befindet. Das ist notwendig bei Listen - genauer gesagt bei TObjectList -, um einen Mehrfachaufruf des Destructors zu vermeiden. Zu erwähnen wäre noch _ReleaseSave: Damit wird das unangenehme Verhalten von TInterfacedObject vermieden, daß das Objekt nach Abfrage des Interfaces gleich wieder zerstört wird, wenn vorher der Referenzzähler nicht erhöht wurde:
Delphi-Quellcode:
procedure TForm1.Button3Click(Sender: TObject);
var o: TInterfaceObject; begin o:=TInterfacedObject.Create; if Supports(o,IInterface) then ; o. ...; //<- geht schief, da das Objekt inzwischen freigegeben wurde end; Listing 10
Delphi-Quellcode:
procedure TForm1.Button4Click(Sender: TObject);
var o1, o2: INotifyObject; begin o1:=TNotifyObject.Create; o2:=o1; o1.Free; //sowohl o1 als auch o2 sind jetzt nil!!! end; Listing 11
Delphi-Quellcode:
procedure TForm1.Button5Click(Sender: TObject);
var o: TNotifyObject; l: TList; begin o:=TNotifyObject.Create; o.AddRef(o); //<- bei Freigabe Variable auf nil setzen l:=TList.Create; try l.Add(o); o.Free; //Abmeldung bei der Liste und o auf nil setzen ShowMessage(IntToStr(l.Count)); if not assigned(o) then ShowMessage('o=nil'); finally l.Free end; end; Listing 12 Die Methode Free des Objektes kann auch bei nil ausgeführt werden, da sie immer existiert und erst bei der Ausführung getestet wird, ob das Objekt vorhanden ist. Im Gegensatz dazu führt der Versuch, die Interface-Methode bei nil aufzurufen, zu einer Zugriffsverletzung. Geplant ist, diese Technik in der kommenden Version der Frameworks einzusetzen. Im Gegensatz zu stahli's Lösung ist sie aber so allgemein gehalten, daß alle Objekte, die das INotify-Interface unterstützen oder einfach von TNotifyObject abgeleitet werden, an diesem Mechanismus teilhaben können. Die wichtigsten Listen TList, TObjectList, TThreadList und TInterfaceList wurden mit dieser Schnittstelle ausgerüsten und stehen so allgemein zur Verfügung. |
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Die Lösung funktioniert und ist elegant, keine Frage, aber Ich kann mir nicht helfen: Ich sehe darin einen Versuch, schlechtes Design nachträglich zu vertuschen.
Es ist doch so: Wenn ich in einer Klasse einen Verweis auf ein externes Objekt habe, das jederzeit freigegeben werden könnte, dann breche ich mir doch einen ab, Code für diesen Verweis zu schreiben, weil ich jederzeit und immer befürchten muss, das mir der Zeiger unterm Arsch weggezogen wird. Sicheren Code kann ich ohne zusätzliche Verwendung von Critical Sections so nicht schreiben:
Delphi-Quellcode:
Mit weniger Code auskommen heißt nicht, es besser zu machen. 'Kompakter' Code ist nicht gleichbedeutend mit wartbar, lesbar oder robust. In meinen Augen ist es der Lesbarkeit nicht dienlich, solche Seiteneffekte zu implementieren. Ohne zusätzliche Aktionen ist o.g. Code zwar augenscheinlich sicher, aber der Fehler ist nur sehr schwer zu lokalisieren ("Wieso ist der Pointer auf einmal nil? Ich hab doch extra eine Abfrage eingebaut?")
If Assigned (FMyMarker) Then // <--- klappt noch
// hier wird der Marker freigegeben FMyMarker.Bar(); // <--- PENG In diesem Fall würde ich mit einem Handle arbeiten, und bei der Verwendung eines Markers explizit ein (Singleton-)Objekt anfordern, die Arbeiten ausführen und dann, wenn ich fertig bin, wieder zurückgeben, ähnlich einer TThreadList. Das bedeutet, das der Code innerhalb der Anforderung/Rückgabe 100% sicher ist.
Delphi-Quellcode:
Wer nun meint, das das mit dem Try..Finally schlecht(er) lesbar ist, der refaktorisiert das eben in eine lokale Methode mit aussagekräftigem Namen.
MyMarker := MyGoogleContainer.LockObject(FMyMarkerHandle);
If Assigned(MyMarker) Then Try MyMarker.Foo(); Finally MyGoogleContainer.Unlock(MyMarker); End; Die entgültige Freigabe des Objektes obliegt dem Erzeuger, hier vermutlich dem Skript-Objekt oder dem Google-Maps Framework. Der Verwender des Frameworks darf natürlich explizit die Marker entgültig freigeben, allerdings nicht direkt, sondern über eine Methode des Frameworks ('ReleaseMarker'); Eine anschließende Anforderung eines (mittlerweile ungültigen) Handles liefert ein NIL-Objekt zurück, auf das ich explizit und gewollt reagieren kann. Aber vielleicht habe ich das entscheidende übersehen: Wie löst Du das Problem des 'unterm Arsch wegziehen'? |
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Ich würde das Ding, ganz einfach nur als Strohdoofes und ganz normales Interface auslegen.
Es wird freigegeben wenn alle Referenzen auf nil stehen und es verändert nicht von innen her seine Referenzen. Wenn der Punkt nicht mehr sichtbar sein soll, wird er nur aus den Googleobjekten/-listen rausgenommen und maximal in den Punkt-Objekt noch ein Flag gesetzt "ich bin gelöscht", welches man auswerten könnte, wenn doch noch jemand mal auf eine Methode des Objektes zugreifen will, obwohl es "eigentlich" nicht mehr existiert, bzw. nicht mehr angezeigt wird. Problem: Man markiert eine Speicherposition 'ner lokalen Variable, die Prozedur wurde schon verlassen und Variable existiert nicht mehr. Oder man markiert ein Feld in einem Objekt, gibt das Objekt frei und vergist die Markierung zu entfernen. Wird jetzt das Objekt freigegeben und werden dann alle markierten Referenzen auf nil gesetzt, dann würden "falsche" Speicherbereiche überschrieben. Ich wünsche dem armen Kerl/Mädl schonmal viel Spaß, welche(r) diesen Bug dann suchen darf, wo sich unvorhersehbar irgendwo etwas verändert, welches quasi einem Bufferoverrun ähnelt. |
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Sowas passiert halt, wenn man Code aus einer GC Sprache 1 zu 1 in Delphi übersetzt ;)
|
AW: Intelligente Objekte - automatische Freigabe von Referenzen
In Delphi haben wir ja auch sowas wie einen GC und das nennt sich Interface (String und dyn. Array gehört auch mit dazu).
|
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Zitat:
|
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Zitat:
Für COM mag das zwar einen gewissen Sinn haben, aber Interfaces in einer objektorientierten Sprache immer per Ref-Count zu verwalten ist im allgemeinen grober Unfug. Auf Design-Fehlern dann irgendwelche Framework-Spezialitäten aufzubauen halte ich für äußerst fragwürdig... |
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Blöde Frage: Woher weiß ein GC, wann ein Objekt nicht mehr benötigt wird?
Nur durch den Scope? Oder wird nicht auch hier eine Art Referenzzähler verwendet? Außerdem verstehe ich nicht, was an Referenzzählern grober Unfug sein soll. Ich dachte immer, das die Strings genauso verwaltet werden. In diesem Fall wäre das zumindest eine sichere Alternative. |
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Der GC merkt sich nicht nur die Anzahl der Referenzen, sondern auch die Addressen der Referenzen selber ... er weiß also wo alle Referenzen liegen.
In diesem Sinne ist eine "billige" Referenzzählung wesentlich einfacher. > nur ein Integer, anstatt einer rießigen Liste Und nein, bei Interfaces müssen die referenzen gezählt werden, sonst weiß man ja nicht wann es keine mehr gibt, um das Interface dann zreitugeben. Aber ja, auch interfaces kann man ohne Referenzzählung nutzen. Siehe TComponent. Denn dieses kann man auch über ein Interface ansprechen, allerdings ohne Zählung, da dort das Objekt nicht über die Interfacereferenzen freigegeben wird, sonder über Free, denn sonst würde das Objekt schon gelöscht, wenn die letzte Interfacereferenz weg ist, aber irgendwo könnten dann "ungültige" Objektreferenzen rumliegen. |
AW: Intelligente Objekte - automatische Freigabe von Referenzen
Zitat:
Das Konzept beim Einsatz von Interfaces ist schon etwas vielschichtiger, so dass der Aspekt des RefCounting nur einer von vielen ist. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:36 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