AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign Delphi Intelligente Objekte - automatische Freigabe von Referenzen
Thema durchsuchen
Ansicht
Themen-Optionen

Intelligente Objekte - automatische Freigabe von Referenzen

Ein Thema von Thom · begonnen am 5. Mär 2012 · letzter Beitrag vom 7. Mär 2012
Antwort Antwort
Seite 1 von 3  1 23      
Thom

Registriert seit: 19. Mai 2006
570 Beiträge
 
Delphi XE3 Professional
 
#1

Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 03:40
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 Referenzen auf ungültige Objekte.

Jedem ist (sollte) bekannt (sein), daß nach
Delphi-Quellcode:
var
  o1, o2: TObject;
begin
[...]
  o1:=TObject.Create;
  o2:=o1;
  o1.Free;
[...]
end;
Listing 1
sowohl o1 als auch o2 noch auf ein (nicht mehr gültiges) Objekt verweisen. Auch das verteufelte FreeAndNil schafft nur teilweise Abhilfe:
Delphi-Quellcode:
var
  o1, o2: TObject;
begin
[...]
  o1:=TObject.Create;
  o2:=o1;
  FreeAndNil(o1);
[...]
end;
Listing 2
In diesem Fall bleibt o2 erhalten, auch wenn o1 auf nil gesetzt wurde.

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 Framework für Google Maps:
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
In der Methode Button1Click werden zwei Objekte neu erstellt: TMarker und TPoint. Der Marker wird an zwei Stellen gespeichert: In FMyMarker (Stelle 1) und - nicht offensichtlich - in der Marker-Liste des Script-Objektes, also TScript.Markers (Stelle 2). Zusätzlich melden sich beide Objekte in einer frameworkinternen Liste an, um Speicherlecks zu vermeiden. Das TPoint-Objekt wird so spätestens bei Beendigung des Programmes freigegeben.
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
Leider macht das den Quelltext nicht gerade übersichtlicher, kompakter oder JavaScript-ähnlich. Abhilfe würde hier die Verwendung von Interfaces mit ihrer automatischen Referenzzählung bieten:
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
oder so kurz wie in Listing 3 ohne lokale Variable. TPoint würde in diesem Fall nach der Zuweisung zu TMyMarker.Position sofort automatisch freigegeben oder bei Listing 5 nach Verlassen der Methode. So weit - so gut.
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
Die Notwendigkeit der mehrfachen Freigabe macht die Programmierung nicht gerade übersichtlicher und bedeutet gegenüber der Nutzung reiner Objekte einen Rückschritt.
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
Daraufhin wäre FMyMarker nil und der Marker ist nicht mehr in der Liste TScript.Markers enthalten.
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
den Marker aus der Liste und setzt FMyMarker auf nil.
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
Es unterstützt drei Verfahren zur Referenzverwaltung:
  1. Die automatische Referenz auf Variablen im Speicher (lokal und global) unter Verwendung von Interfaces.
  2. Die manuelle Referenz auf Variablen im Speicher (lokal und global) unter Verwendung der Methode AddRef().
  3. Die Benachrichtigung über die Freigabe unter Verwendung der Methode AddNotify().
Zusätzlich kann zur Kontrolle der Stand des Interface-Referenzzählers über RefCount beziehungsweise GetRefCount ausgelesen werden.
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
Konkret sieht das so aus:
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
Listen benutzen die Benachrichtigungsmethode, da direkte Speicherreferenzen fatale Fehler ergeben würden: Das Einfügen und Entfernen von Elementen führt zu einer Verschiebung der Zeiger und damit zu veränderlichen Speicheradressen. Bei Listen, die das INotify-Interface unterstützen, sieht das folgendermaßen aus:
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
Eine wichtiger Unterschied besteht allerdings bei den Aufrufen von TNotifyObject.Free und INotify.Free:
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.
Thomas Nitzschke
Google Maps mit Delphi
  Mit Zitat antworten Zitat
Furtbichler
(Gast)

n/a Beiträge
 
#2

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 08:01
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:
  If Assigned (FMyMarker) Then // <--- klappt noch
                               // hier wird der Marker freigegeben
    FMyMarker.Bar(); // <--- PENG
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?")

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:
MyMarker := MyGoogleContainer.LockObject(FMyMarkerHandle);
If Assigned(MyMarker) Then
  Try
    MyMarker.Foo();
  Finally
    MyGoogleContainer.Unlock(MyMarker);
  End;
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.

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'?
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#3

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 08:11
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.
$2B or not $2B

Geändert von himitsu ( 5. Mär 2012 um 08:16 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

Registriert seit: 12. Aug 2003
Ort: Soest
4.027 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#4

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 08:30
Sowas passiert halt, wenn man Code aus einer GC Sprache 1 zu 1 in Delphi übersetzt
Stefan
“Simplicity, carried to the extreme, becomes elegance.” Jon Franklin

Delphi Sorcery - DSharp - Spring4D - TestInsight
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#5

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 08:37
In Delphi haben wir ja auch sowas wie einen GC und das nennt sich Interface (String und dyn. Array gehört auch mit dazu).
$2B or not $2B
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

Registriert seit: 12. Aug 2003
Ort: Soest
4.027 Beiträge
 
Delphi 10.1 Berlin Enterprise
 
#6

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 08:47
In Delphi haben wir ja auch sowas wie einen GC und das nennt sich Interface (String und dyn. Array gehört auch mit dazu).
Interfaces und andere managed data types sind referenzgezählt. Da ist noch ein himmelweiter Unterschied zu einem GC.
Stefan
“Simplicity, carried to the extreme, becomes elegance.” Jon Franklin

Delphi Sorcery - DSharp - Spring4D - TestInsight
  Mit Zitat antworten Zitat
Patito

Registriert seit: 8. Sep 2006
108 Beiträge
 
#7

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 10:26
In Delphi haben wir ja auch sowas wie einen GC und das nennt sich Interface (String und dyn. Array gehört auch mit dazu).
Wobei ich hier anzumerken hätte, dass es sich beim Referen-Zähler von Interfaces hauptsächlich um einen Design-Fehler von Delphi handelt. (Die Designer hatten damals wohl zu viel COM im Kopf).
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...
  Mit Zitat antworten Zitat
Iwo Asnet

Registriert seit: 11. Jun 2011
313 Beiträge
 
#8

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 10:31
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.

Geändert von Iwo Asnet ( 5. Mär 2012 um 10:42 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.184 Beiträge
 
Delphi 12 Athens
 
#9

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 10:54
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.
$2B or not $2B
  Mit Zitat antworten Zitat
neo4a

Registriert seit: 22. Jan 2007
Ort: Ingolstadt
362 Beiträge
 
Delphi XE2 Architect
 
#10

AW: Intelligente Objekte - automatische Freigabe von Referenzen

  Alt 5. Mär 2012, 11:01
Auf Design-Fehlern dann irgendwelche Framework-Spezialitäten aufzubauen halte ich für äußerst fragwürdig...
Und deshalb machst Du jetzt genau was? Drei Punkte!?

Das Konzept beim Einsatz von Interfaces ist schon etwas vielschichtiger, so dass der Aspekt des RefCounting nur einer von vielen ist.
Andreas
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 3  1 23      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:45 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz