|
Antwort |
Wenn in Delphi ein Objekt aus dem Leben scheidet dann gibt es nur einen einzigen Weg dies zu tun -
über den Destruktor. Borland hat den Destruktor schon in der Vaterklasse TObject deklariert
Delphi-Quellcode:
Zu jeder Klasse gibt eine VMT (virtual Method Table) und die Destroy-Methode hat dort einen festen Eintrag.
TObject = class
... public ... destructor Destroy; virtual; end; Der Index innerhalb der VMT ist mit der Konstanten vmtDestroy=-4 festgelegt. Es gibt nur einen einzigen Destruktor. Daraus kann man die Nebenbedingungen schlussfolgern * der Destructor muss immer Destroy heisen * der Destruktor kann keine Parameter haben und er hat auch keinen Rückgabewert * der Destruktor muss immer mit override überschrieben werden * der Destruktor darf keine eingeschränkte Sichtbarkeit haben; er ist also immer public destructor Destroy; override; Jede Abweichung von dieser Deklaration ist falsch. Hier ist ein Beispiel mit mehreren falsch deklarierten Destruktoren:
Delphi-Quellcode:
Warum hat der Destruktor überhaupt einen Namen, wenn es sowieso nur einen einzigen gibt?
TMeineKlasse = class(TPersistent)
private destructor Destroy; // Falsch - override fehlt, darf nicht private sein public destructor Destroy; override; // Richtig destructor GibFrei; // Falscher Name destructor Destroy; reindroduce; // Falsch da so die VMT ignoriert wird end; Hätte der Destruktor keinen Namen, gäbe es keine Möglichkeit, ihn direkt aufrufen Warum ist der Destruktor mit der Sichtbarkeit public deklariert, hätte man ihn nicht auch protected machen können? Borland wollte offensichtlich dem Programmierer die Möglichkeit geben, den Destruktor direkt aufzurufen. Warum gibt es eigentlich nur einen einzigen Destruktor? Wenn ein Objekt freigeben wird, dann wird es nicht mehr gebraucht. Derjenige, der das Objekt freigibt hat oftmals kein Wissen um was für ein Objekt es sich handelt. Genauer gesagt, der Code, der ein Objekt freigibt soll gar keine näheren Infos zu Internas des Objekt haben. (Geheimnisprinzip des OOP) Der Besitzer des Objekts sagt "Stirb!" und das Objekt hat zu gehorchen. Was ist der Unterschied zwischen Free und Destroy? Free ruft Destroy auf, abr nur wenn das Objekt überhaupt existiert; also der self-Pointer nicht nil ist. Am Besten sieht man das, wenn man sich die Free-Prozedur anschaut.
Delphi-Quellcode:
Darf man Destroy überhaupt aufrufen? Es heisst doch immer man solle Free verwenden.
procedure TObject.Free;
begin if Assigned(self) then Destroy; end; Ja, man darf Destroy direkt aufrufen und man kann so einige CPU-Takte sparen. Aber man darf dies nur tun, wenn man 1000% sicher ist dass man das Objekt selbst erzeugt hat:
Delphi-Quellcode:
Welcher Code gehört in einen Destruktor?
var
meinobj : TMeineKlasse; begin meinobj := TMeineKlasse.Create; try meinobj.Machwas; meinobj.MasWasAnderes(42); finally // ja, das ist erlaubt aber nicht empfehlenswert // man spart hier die Überprüfung ob meinobj <> nil ist // aber es besteht die Gefahr, dass der Code mal verändert wird // und dann die Änderung von Destroy nach Free vergessen wird meinobj.Destroy; end; end; Auf jeden Fall sollte der Aufruf von Inherited die letzte Zeile im Destruktor sein.
Delphi-Quellcode:
Man sollte im Destruktor nur belegte Resourcen (also eingebette Objekte, Window-Handles) freigeben.
destructor TMeinKlasse.Destroy;
begin FList.Free; inherited; end; Grössere Aktionen wie z.B. Speichern in einer Datenbank sollten vermieden werden. Soll ich einen Destruktor auch dann verwenden, wenn gar nicht zu tun ist? Nein, ein Destruktor sollte man nur dann deklarieren, wenn auch etwas zu tun ist.
Delphi-Quellcode:
Wenn ich direkt von TObject ableite, dann brauche ich doch das inherited im Destruktor gar nicht aufrufen, oder?
// schlechter Stil: unnötiger Code und unnötiger Eintrag in der VMT
destructor TMeineKlasse.Destroy; begin inherited; end; Das ist im Prinzip richtig, aber man sollte als Programmierer nicht versuchen übertrieben clever zu sein. Niemand weiss nicht ob nicht jemand in Zukunft die Klasse von einer anderen Basisklasse ableitet und dann entsteht ein Resourcen-/Speicherleck. Was sind die häufigsten Fehler beim Destruktor? Das override bei der Deklaration zu vergessen oder das inherited innerhalb des Destruktor zu vergessen sind die Klassiker. Warum braucht man überhaupt das inherited im Destruktor? Könnte Delphi das nicht automatisch aufrufen? Gute Frage! In anderen Programmiersprachen (z.B. C++, C#) ist es tatsächlich so, dass automatisch die ererbten Destruktoren aufgerufen werden, ohne dass sich der Programmierer darum kümmern müsste. |
Delphi 12 Athens |
#2
Zitat:
// schlechter Stil: unnötiger Code und unnötiger Eintrag in der VMT
- ja, es ist unnötiger Code - nein, es ist kein unnötiger Eintrag in der VMT Denn die VMT gibt es so oder so. - hat man was überschrieben, dann steht die eigene Methode drin - wurde es nicht überschrieben, dann steht darin die letzte Methode des Vorfahren Und das Destroy immer public sein muß, stimmt auch nicht. Da diese Methode über die VMT und nicht über die RTTI gesucht wird, kann sie als alles deklariert überschrieben sein, da Free bei Freigabe über TObject und dessen Sichtbarkeiten geht. Nur daß Nachfahren es schwer hätten das zu überschreiben, wenn es private ist. Ich persönlich hätte dieses Destroy als Protected deklariert, was zu weniger Problemen geführt hätte. (kann muß Free aufrufen, da Destroy nicht von außen erreichbar ist)
Zitat:
Borland wollte offensichtlich dem Programmierer die Möglichkeit geben, den Destruktor direkt aufzurufen.
In der Deklaration von TObject findet man garnichts von Sichtbarkeiten ... das hatte man wohl einfach vergessen. Das ist zumindestens in älteren Delphis so, was man inzwischen (D2010) geändert hat, aber vermutlich aus Gründen der Abwärtskompatibilität hat man lediglich das Public eingebaut und nicht gleich mal Einiges nach Protected verschoben. Wobei ich diese angebliche Abwärtskomatibilität, wie gesagt, eh sinnlos finde, da es im Notfall dennoch sichtbar gemacht werden könnte, dort wo es wirklich unbedingt nötig währe. Oftmals sind auch Methoden als Published deklariert, obwohl sie eigentlich nur als Public gedacht waren, denn meistens wird zu Anfang garkeine Sichtbarkeit angegeben, da kommt ohne Angabe gleich die erste Methode in der Deklaration. Und bei TPersistent-Nachfahren wurde die Standardsichtbarkeit von Public auf Published geändert wurde, womit diese Methoden dann automatisch Published sind. Oder Schlimmer, vieles ist Private, was besser Protected gewesen währe.
Zitat:
Warum braucht man überhaupt das inherited im Destruktor? Könnte Delphi das nicht automatisch aufrufen?
Gute Frage! In anderen Programmiersprachen (z.B. C++, C#) ist es tatsächlich so, dass automatisch die ererbten Destruktoren aufgerufen werden, ohne dass sich der Programmierer darum kümmern müsste. Und außerdem kann man über das Inherited entscheiden wann der Vorfahr ausgeführt wird. (zu Anfang, zwischendrin, am Ende oder garnicht) Jetzt fehlt nur noch der Constructor. Auch der Class Constructor und Class Destructor währen eine Erwähnung wert. Das sind die neuen Initialization und Finalization, aber ohne gewisse Nachteile, welche viele kennen und weswegen oftmals bei NonVCL Units wie SysUtils weggelassen werden. (Dateigröße) Geändert von himitsu (15. Apr 2012 um 07:34 Uhr) |
Zitat |
Delphi 2007 Professional |
#3
Zitat:
Warum braucht man überhaupt das inherited im Destruktor? Könnte Delphi das nicht automatisch aufrufen? ...
Eine abgeleitete Klasse kann und darf nicht wissen, was in den Basisklassen über ihr freigegeben wird. Und außerdem kann man über das Inherited entscheiden wann der Vorfahr ausgeführt wird. (zu Anfang, zwischendrin, am Ende oder garnicht)
Die Freiheit in Delphi zuerst die ererbten Resourcen freigeben zu können und dann erst die eigenen Resourcen freizugeben sollte die absolute Ausnahme bleiben. Ein Destruktor, der kein inherited aufruft, muss als potentielles Resourcen-/Speicherleck betrachtet werden. Der Compiler sollte dies als Fehler betrachten oder zumindest eine Warnung ausgeben. Jetzt fehlt nur noch der Constructor. ...Class Constructor ...Class Destructor
|
Zitat |
Delphi 12 Athens |
#4
Beim Destruktor ist das verboten! Die Kette der Destruktoren muss abgearbeitet werden; sie darf nicht unterbrochen werden. Falls doch, droht ein Resourcen-/Speicherleck.
(praktisch für einen Bugfix)
Zitat:
Resourcen (Speicher, Handles,...) sollten immer in umgekehrter Weise freigegeben werden, in der sie angefordert wurden; das ist der sicherste Weg.
|
Zitat |
Delphi 2007 Professional |
#5
Die Kette der Destruktoren muss abgearbeitet werden; sie darf nicht unterbrochen werden. Falls doch, droht ein Resourcen-/Speicherleck.
1.) er muss die lokalen belegten Resourcen seiner Klasse freigeben! 2.) er muss die Resourcen freigeben, die seiner Klasse überantwortet wurden Zum Beispiel übernimmt die Klasse TComponent die Verantwortung dafür, dass alle Objekte im Components[]-Array im Destruktor entsorgt werden. 3.) er muss seiner Vaterklasse die Möglichkeit geben sich selbst aufzuräumen in dem er inherited aufruft. Was im Destruktor der Vaterklasse passiert und ob dessen Konstruktor aufgerufen wurde oder nicht hat nicht zu interessieren. Der Sourcecode der Vaterklasse muss als Blackbox betrachtet werden; es muss völlig egal sein was in der Vaterklasse an Resourcen belegt wurde oder nicht. Wichtig ist allein nur der Vaterklasse über den Aufruf von inherited die Chance zu geben, alle belegten Resourcen zu entsorgen. 4.) er muss so programmiert sein, dass selbst wenn ein Objekt nur teilweise initialisiert wurde (z.B. Exception im Konstructor) er gefahrlos die bislang belegten Resourcen freigibt. Das sind die Regeln; das ist der (ungeschriebene) Vertrag der bei Ableitung einer Klasse geschlossen wurde. |
Zitat |
Delphi 12 Athens |
#6
In 99,9% aller Fälle sind deine Regeln zutreffend, es kann Ausnahmefällen geben, allerdings sollte eine Regel immer eingehalten werden: Der Destructor "Destroy" muss immer in der Lage sein das Objekt korrekt freizugeben.
Ein kleines Beispiel mit Zirkelbezug:
Delphi-Quellcode:
TPartnerObject = class(TObject)
protected FPartner: TPartnerObject; public // darf nicht mehr überschrieben werden destructor Destroy; override; finally; // kann in abgeleiteten Klassen überschrieben werden destructor MyDestroy(Sender: TObject); virtual; property Partner: TPartnerObject read FPartner write FPartner; end; // bleibt weiterhin voll funktionsfähig destructor TPartnerObject.Destroy; begin MyDestroy(Self); end; // Destroy ruft immer das für die Klasse gültige MyDestroy auf destructor TPartnerObject.MyDestroy(Sender: TObject); begin if Assigned(FPartner) and (FPartner <> Sender) then FPartner.MyDestroy(Sender); inherited Destroy; end; Kette := TPartnerObject.Create; Kette.Partner := TPartnerObject.Create; Kette.Partner.Partner := TPartnerObject.Create; Kette.Partner.Partner.Partner := Kette; FreeAndNil(Kette); |
Zitat |
|
#7
Delphi-Quellcode:
Wuhaha, Kreis am Stiel
Kreis := TPartnerObject.Create;
Kreis.Partner := TPartnerObject.Create; Kreis.Partner.Partner := Kreis; Stiel := TPartnerObject.Create; Stiel.Partner = Kreis; Stiel.free(); // Stackoverflow? (Ungetestet: hab grad kein Delphi zur Hand) Eine Variante, die auch dieses Problem lösen sollte:
Delphi-Quellcode:
Das würde bei auch mehreren Partnern funktionieren und entspricht der Tiefensuche in einem Graphen.
TPartnerObject = class(TObject)
private FDieing: boolean; protected FPartner: TPartnerObject; public constructor Create; destructor Destroy; override; property Partner: TPartnerObject read FPartner write FPartner; end; constructor TPartnerObject.create; begin FDieing := false; FPartner := nil; end; destructor TPartnerObject.Destroy; begin FDieing := true; if assigned(Partner) then if not FPartner.Dieing then FPartner.Destroy; // ist schon auf nil überprüft inherited; end; |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |