![]() |
Warum virtual / override bei destructor / constructor?
Hallo! Ich möchte endlich mal verstehen, warum es so ist. In der Unit System wird TObject eingeführt und der Constructor ohne "virtual", aber der Destructor mit "virtual" deklariert.
Entsprechend muss man bei der Ableitung beim Destructor das Schlüsselwort "override" angeben, während man beim Constructor einfach so Create() überschreiben kann. In beiden Routinen kann und soll man dann mit "inherited" die Vorfahrroutine aufrufen. WARUM bloß ist das so? Warum ist bei Create kein "virtual" deklariert und es geht trotzdem alles und warum braucht man beim Destructor das "virtual > override"? Hätten die Entwickler von TObject nicht beide Prozeduren analog deklarieren können? Also beide mit oder beide ohne "virtual"? Gibt es einen Grund, warum es so ist? Mich irritiert es schon immer, dass ich bei Create ohne override auskomme, bei Destroy es aber angeben muss, sich aber beide Prozeduren ansonsten identisch zu verhalten scheinen, abgeleitet und überschrieben werden können und "inherited" auch verwendbar ist. Danke im voraus für jede Erhellung des Sachverhalts!
Delphi-Quellcode:
und die Verwendung in abgeleiteten Klassen:
{ unit System }
TObject = class public constructor Create; { ...} protected destructor Destroy; virtual; {... } constructor TObject.Create; begin end; destructor TObject.Destroy; begin end;
Delphi-Quellcode:
TMyObject = class(TObject)
constructor Create; destructor Destroy; override; { ...} implementation constructor TObject.Create; begin inherited; { ... } end; destructor TObject.Destroy; begin { ... } inherited; end; |
AW: Warum virtual / override bei destructor / constructor?
Delphi-Quellcode:
var
AObj: TObject; begin AObj := TMyObj.Create; // hier gibst du eh konkret an, ob ein TMyObj oder ein TObject oder sonstwas erstellt werden soll, auch wenn die Variable als TObject deklariert ist ... AObj.Destroy; // bzw. Free; // ohne das virtual würde hier der Destructor von TObject aufgerufen, mit virtual wird korrekterweise der von TMyObj aufgerufen end; |
AW: Warum virtual / override bei destructor / constructor?
Danke für die schnelle Antwort. Aber warum funktioniert das Ableiten von Create auch ohne "virtual"? Das Konzept, warum "virtual" mal nötig und mal nicht nötig ist, leuchtet mir nicht ein.
Warum ruft "inherited" trotzdem die richtige Vorfahrroutine auf und ist verwendbar? Ich hätte eigentlich gedacht, dass man "virtual" deklarieren muss, wenn man möchte, dass die Klasse überschrieben werden kann? Es ist ja nicht einmal ein "reintroduce" nötig, man kann einfach Create neu definieren ohne Probleme... |
AW: Warum virtual / override bei destructor / constructor?
Das inherited hat damit nicht direkt was zu tun. Damit sagst du nur, ob und an welcher Stelle die Methode des Vorfahren aufgerufen wird, was man in der Regel will, um nicht alles neu implementieren zu müssen bzw. bei Änderungen alle Nachfahren anfassen zu müssen.
Das Virtual bedeutet nur, dass das Programm zur Laufzeit prüft ob es in obigem Beispiel wirklich ein TObject (so ist die Variable deklariert) oder ein Nachfahre davon ist. Ist es ein Nachfahre wird dessen Destructor aufgerufen. Ohne das Virtual würde in diesem Fall TObject.Destroy aufgerufen, was ja falsch wäre. |
AW: Warum virtual / override bei destructor / constructor?
Virtual bei
Delphi-Quellcode:
benötigt man z.b. in folgendem Falle:
Create
Delphi-Quellcode:
Hätte man den Constructor hier nicht als
type
TBaseClass = class(TObject) public constructor Create; virtual; end; TClassA = class(TBaseClass) public constructor Create; override; end; TClassB = class(TBaseClass) public constructor Create; override; end; TClassType = class of TBaseClass; .. function DynamicCreate(ClassType: TClassType): TBaseClass; begin Result := ClassType.Create; end;
Delphi-Quellcode:
deklariert, würde immer Der von
virtual
Delphi-Quellcode:
aufgerufen. Mit
TBaseClass
Delphi-Quellcode:
ruft die Funktion korrekt den Constructor der konkreten Klasse auf.
virtual
|
AW: Warum virtual / override bei destructor / constructor?
Das bedeutet dann aber auch, dass die Entwicklung der Unit System problemlos sowohl den Constructor Create als auch den Destructor Destroy hätten analog mit "virtual" deklarieren können und alles funktionieren würde? Ist das so?
Dieses Analogie der Deklarationen würde sicherlich vielen Anfängern Irritationen ersparen. |
AW: Warum virtual / override bei destructor / constructor?
Zitat:
|
AW: Warum virtual / override bei destructor / constructor?
OK, danke.
Warum kann man den "constructor create" überschreiben, ohne "reintroduce" angeben zu müssen? Müsste das nicht kollidieren? Wenn auch ohne "virtual/override" eine Vorfahrprozedur überschrieben und verdeckt werden kann und trotzdem mittels "inherited" die Vorfahrprozedur aufgerufen werden kann, dann verstehe ich eigentlich nicht, wozu man überhaupt "virtual/override" braucht? |
AW: Warum virtual / override bei destructor / constructor?
Was Zacherl gezeigt hat, sieht man zum Beispiel bei TComponent. Dort ist der Konstructor virtual, was benötigt wird, wenn vom Streaming Mechanismus (Form wird aus dfm geladen) die ganzen Komponenten erstellt werden.
Wäre TObject.Create virtual, müsste man jegliche Klasse über TClass erzeugen können, obwohl diese Klasse möglicherweise gar keinen parameterlosen Konstruktor haben soll. Aktuell gibt es nur den kleinen haken, dass ein Create auf einer TClass variable fest TObject.Create aufruft, was weitaus weniger schlimm ist. Zitat:
Mir scheint, da sind noch ein paar Wissenslücken bzgl OOP/Polymorphie verhanden, die das Verständnis etwas erschweren. P.S. Da sieht man mal, warum C# es erforderlich macht, auf jeder Klasse explizit einen Konstruktor zu definieren (selbst wenn er nix macht). Weil implizit einen erben nur verwirrt und zu Defekten führen kann. |
AW: Warum virtual / override bei destructor / constructor?
Zitat:
Zitat:
Der Unterschied ist, ob zur Laufzeit die Methode des echten Typs (ggf. Nachfahren) oder die des Variablentyps aufgerufen wird |
AW: Warum virtual / override bei destructor / constructor?
Zitat:
Delphi-Quellcode:
Nehmen wir mal diesen trivialen Fall. Alles Funktioniert. Keine Warnungen durch den Compiler. Man kann auch in der Child-Klasse die Parent-Methode mittels inherited nutzen.
TMyParent = class(TObject)
public procedure Something; end; TMyChild = class(TMyParent) public procedure Something; end; implementation { TMyParent } procedure TMyParent.Something; begin Form1.Memo1.Lines.Add('Parent'); end; { TMyChild } procedure TMyChild.Something; begin inherited Something; Form1.Memo1.Lines.Add('Child'); end; Auch wenn ich nun beim Parent ein "virtual" hinzufüge, ändert sich nichts am Verhalten des Programms, außer dass es eine W1010 Warnung vom Compiler gibt. Die Methode "Something" wird mit und ohne "virtual" verdeckt und die verdeckte Methode kann so oder so mit "inherited" verwendet werden. Wieso ist sie angeblich in dem einen Fall "verdeckt" und im anderen Fall nicht? Was bringt die Angabe von "virtual", wenn man die Methode doch ohnehin mittels inherited weiter verwenden kann?
Delphi-Quellcode:
Was ändert sich durch "virtual"?
TMyParent = class(TObject)
public procedure Something; virtual; end; TMyChild = class(TMyParent) public procedure Something; { kein "override" nötig! } end; In so vielen Büchern steht, dass eine abgeleitete Klasse eine virtuelle Prozedur überschreiben kann. Aber das geht doch auch ohne "virtual", siehe Beispiel! Oder zurück zur Titelfrage: Was würde passieren, wenn "TObject.Destroy" nicht virtual deklariert wäre und abgeleitete Klassen einfach wie im obigen Beispiel Destroy neu implementieren und über inherited die Parent-Prozedur aufrufen würden? Wäre irgendwas anders, als es jetzt ist? |
AW: Warum virtual / override bei destructor / constructor?
Delphi-Quellcode:
Das führst jetzt einmal ohne und einmal mit virtual+override aus, dann siehst du im Ergebnis den Unterschied.
procedure Test;
var A: TMyParent; begin A := TMyChild.Create; try A.Something; finally A.Free; end; end; |
AW: Warum virtual / override bei destructor / constructor?
Vielen Dank, OK, das verstehe ich jetzt! Gute Erklärung als Beispiel!
Umso mehr erscheint es doch aber dann als Fehler, dass TObject.Create eben gerade nicht virtuell deklariert ist...? Das war ja die Titelfrage dieses Threads... |
AW: Warum virtual / override bei destructor / constructor?
Zitat:
|
AW: Warum virtual / override bei destructor / constructor?
Zitat:
|
AW: Warum virtual / override bei destructor / constructor?
Zitat:
|
AW: Warum virtual / override bei destructor / constructor?
Außerdem gibt es nicht nur einen Constructor und die rufen sich auch noch oft gegenseitig auf ... weißt du was das für ein Chaos wird, wenn die alle Virtuell wären, dann weiß man schnell garnicht mehr was in welcher Reihenfolge aufgerufen wird.
Der Destructor heißt in der Regel immer gleich und hat keine Parameter, also geht es da immer schön gradlinig nach oben durch. |
AW: Warum virtual / override bei destructor / constructor?
OK, vielen Dank an alle für Eure Erklärungen!
|
AW: Warum virtual / override bei destructor / constructor?
Bei uns in der Firma ist es Pflicht constructoren virtuell zu machen mit Verweis auf
![]() (reintroduce ist schon noch erlaubt.) |
AW: Warum virtual / override bei destructor / constructor?
Liste der Anhänge anzeigen (Anzahl: 1)
Bin heute zufällig auf diesen Thread gestoßen und weil die Frage interessant klang, durchgelesen. Auch wenn der Thread inzwischen alt ist, glaube ich, dass die Frage noch einige interessiert. Vielleicht liest es jemand noch. Vielleicht kann ich eine einfache Antwort geben. Falls ich richtigliege, wäre es nett wenn es einer bestätigen könnte, ansonsten wiedersprechen. Ich denke Anwort #4 sagt bereits wieso es so ist, aber etwas allgemein.
Warum also sind Destruktoren virtual und Konstruktoren nicht? Die kurze Antwort: Konstruktoren müssen es nicht, Destruktoren sollten es, sonst könnte es sein, dass die nicht korrekt ausgeführt werden. Tabelle siehe Anhang! Um das zu verstehen habe ich eine kleine Tabelle erstellt. Sie beschreibt vier Klassen. TKlasseC ist von TKlasseB abgeleitet, TKlasseB von TKlasseA, TKlasseA von TObject. Alle Klassen haben einen Konstruktor Create und einen Destruktor Destroy. Den Konstruktor Create ignorieren wir vorerst, und konzentrieren und auf Destroy. Wie gesagt, alle Klassen haben ein Create und ein Destroy, aber nur TObject hat Free. Man könnte auch ein eigenes Free in jede Klasse einbauen (also A, B und C), aber wer macht das schon? Es gibt ja das Free von TObject, und das reicht. Es macht ja seinen Job. Also wozu ein eigenes Free programmieren? Und hier liegt meiner Meinung nach des Pudels Kern. Destroy ist wegen Free virtual (u.a.). Nehmen wir mal an ich erstelle die drei Klassen und gebe sie zum Schluss mit Free frei. Jede Klasse (also A, B und C) hat in Create und Destroy einen Code der beim freigeben ausgeführt werden muss. Das Destroy von TObject ist leer, aber alle anderen haben etwas Wichtiges drinstehen. Wenn ich also KlasseC freigebe, müssten die Destroys alle vier Klassen ausgeführt werden. Weil sie voneinander abstammen. Werden sie das? Nehmen wir mal an keiner der Destroys (also von A, B, C und TObject) ist virtual. Was wird alles ausgeführt? Meiner Meinung nach nur das Destroy von TObject. Sonst keines. Warum? Weil in dem Fall das Destroy von TObject statisch ist, also auch eine feste Adresse auf seinen Code bekommt. Warum sollen in dem Fall die anderen Destroys ausgeführt werden? Rufe ich aus TKlasseC Free auf, ruft das das Destroy von TObject auf. Einzig und alleine. Wegen der statischen Adresse. Das Free hat TKlasseC von TObject geerbt, d. h. dieses Free greift eigentlich nur auf das Destroy von TObject zu. Nehmen wir jetzt an das Destroy von TObject ist virtual und von den anderen override. Ruft KlasseC Free auf, ruft das, wie gehabt, das Destroy von TObject auf, weil Free eine Methode von TObject ist. Das hat aber keine statische Adresse auf seinen Code, weil virtual. Also geht es weiter zu Destroy von TKlasseA. Auch das ist nicht statisch, also geht es weiter an das Destroy von KlasseB, und zuletzt an das von KlasseC. Erst das hat eine Adresse auf seinen Code. In dem Fall den Code von Destroy von TKlasseC. Das Programm sagt "To". Nun geht es wieder abwärts, wegen inherited. Nun wird Destroy von TKlasseB aufgerufen, das Programm sagt "Tac", dann von TKlasseA ("Tic") und zuletzt von TObject. Deses Problem hat Create nicht, weil Create direkt aus der jeweiligen Klasse aufgerufen wird. Inherited erledigt dann den Rest, d. h es werden auch die Create von den abgeleiteten Klassen ausgeführt. Destroy wird nicht direkt aufgerufen, sondern über Free. Und das ist nur in TObject vorhanden. Würde man Destroy direkt aufrufen, müsste es nicht virtual sein. Würde man ein Free in TKlasseC programmieren, würde es direkt auf das Destroy von TKlasseC zugreifen. In dem Fall könnte man drauf verzichten Destroy virtual zu machen. Das ist meine Erklärung dazu. Falls ich richtigliege, kann es einer bitte bestätigen? Würde mich interessieren. |
AW: Warum virtual / override bei destructor / constructor?
Create Contructor kann man so viele haben, wie man will ... sie müssen auch nicht Create heißen.
Ob man die virtual macht oder nicht hängt vom Anwendungsfall ab. z.B. bei TComponent, da ruft der DFM-Loader ausschließlich das TComponent.Create auf, daher muß man das auch überschreiben. Egal ob ein Create am Ende anders heißt ... grundsätzlich sollte man immer sicherstellen, dass das originale "Create" (bzw. das Create des Vorfahren) am Ende dennoch immer ausgeführt wird (inherited). Free wird niemals überschrieben, es lässt sich auch garnicht überschreiben. Destroy wird stattdessen überschrieben und man darf niemals das Override vergessen. Genauso ruft man Destroy eigentlich auch nie direkt auf. Free ruft auch auch immer nur TObject.Destroy auf, weswegen man dieses überschreiben muß, damit sichdergestellt wird, dass immer alles ordnungsgemmäß freigegeben wird. Ja, man könnte technisch andere Destructor erstellen, aber da ist nie sichergestellt, dass sie auch aufgerufen werden. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 11:47 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 by Thomas Breitkreuz