Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Warum virtual / override bei destructor / constructor? (https://www.delphipraxis.net/192815-warum-virtual-override-bei-destructor-constructor.html)

HJay 22. Mai 2017 11:41


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:
{ unit System }
TObject = class
public
  constructor Create;
  { ...}
protected
  destructor Destroy; virtual;
   
{... }

constructor TObject.Create;
begin
end;

destructor TObject.Destroy;
begin
end;
und die Verwendung in abgeleiteten Klassen:
Delphi-Quellcode:
TMyObject = class(TObject)
constructor Create;
destructor Destroy; override;
{ ...}

implementation

constructor TObject.Create;
begin
  inherited;
  { ... } 
end;

destructor TObject.Destroy;
begin
  { ... }
  inherited;
end;

Olli73 22. Mai 2017 11:55

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;

HJay 22. Mai 2017 12:03

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...

Olli73 22. Mai 2017 12:10

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.

Zacherl 22. Mai 2017 12:19

AW: Warum virtual / override bei destructor / constructor?
 
Virtual bei
Delphi-Quellcode:
Create
benötigt man z.b. in folgendem Falle:
Delphi-Quellcode:
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;
Hätte man den Constructor hier nicht als
Delphi-Quellcode:
virtual
deklariert, würde immer Der von
Delphi-Quellcode:
TBaseClass
aufgerufen. Mit
Delphi-Quellcode:
virtual
ruft die Funktion korrekt den Constructor der konkreten Klasse auf.

HJay 22. Mai 2017 12:28

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.

Uwe Raabe 22. Mai 2017 12:32

AW: Warum virtual / override bei destructor / constructor?
 
Zitat:

Zitat von HJay (Beitrag 1372318)
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.

Allerdings ist man bei einem virtuellen Konstruktor beim override an die Parameter-Signatur des Vorgängers gebunden. Natürlich kann man trotzdem neue, nicht-virtuelle Konstruktoren einführen, aber das wäre dann für noch mehr Anfänger noch viel verwirrender.

HJay 22. Mai 2017 12:43

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?

Stevie 22. Mai 2017 12:48

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:

Zitat von HJay (Beitrag 1372323)
Warum kann man den "constructor create" überschreiben, ohne "reintroduce" angeben zu müssen? Müsste das nicht kollidieren?

Verwechsle nicht die Begriffe, "überschreiben" (override) kann man nur virtuelle Methoden - ein reintroduce ist lediglich ein Hinweis an den Compiler nach dem Motto - ich weiß, was ich tue (damit der keine Warning W1010 generiert). Alles andere ist verdecken einer Methode. Und da TObject.Create nunmal nicht virtual ist, gibts auch kein reintroduce.

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.

Olli73 22. Mai 2017 12:51

AW: Warum virtual / override bei destructor / constructor?
 
Zitat:

Zitat von HJay (Beitrag 1372323)
Warum kann man den "constructor create" überschreiben, ohne "reintroduce" angeben zu müssen? Müsste das nicht kollidieren?

Weil nicht virtual benutzt wurde.

Zitat:

Zitat von HJay (Beitrag 1372323)
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?

Lies dir nochmals genau die Antworten durch.

Der Unterschied ist, ob zur Laufzeit die Methode des echten Typs (ggf. Nachfahren) oder die des Variablentyps aufgerufen wird

HJay 22. Mai 2017 15:14

AW: Warum virtual / override bei destructor / constructor?
 
Zitat:

Zitat von Stevie (Beitrag 1372324)
Mir scheint, da sind noch ein paar Wissenslücken bzgl OOP/Polymorphie verhanden, die das Verständnis etwas erschweren.

Korrekt. Nun, ich versuche ja gerade, diese Lücken zu füllen... ich programmiere schon lange in OOP, aber gewisse Details leuchten mir einfach partout nicht ein, egal wie viel ich darüber lese.

Delphi-Quellcode:
 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;
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.

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:
 TMyParent = class(TObject)
  public
    procedure Something; virtual;
  end;

 TMyChild = class(TMyParent)
  public
    procedure Something; { kein "override" nötig! }
  end;
Was ändert sich durch "virtual"?

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?

Olli73 22. Mai 2017 15:23

AW: Warum virtual / override bei destructor / constructor?
 
Delphi-Quellcode:
procedure Test;
var
  A: TMyParent;
begin
  A := TMyChild.Create;
  try
    A.Something;
  finally
    A.Free;
  end;
end;
Das führst jetzt einmal ohne und einmal mit virtual+override aus, dann siehst du im Ergebnis den Unterschied.

HJay 22. Mai 2017 15:31

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...

Olli73 22. Mai 2017 15:40

AW: Warum virtual / override bei destructor / constructor?
 
Zitat:

Zitat von HJay (Beitrag 1372350)
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...

Siehe Beiträge #7 und #9.

Uwe Raabe 22. Mai 2017 16:14

AW: Warum virtual / override bei destructor / constructor?
 
Zitat:

Zitat von HJay (Beitrag 1372350)
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...

Gut, dann versuch doch mal in eigenen Worte zu erläutern, für welche Anwendungsfälle man einen virtuellen Konstruktor überhaupt wirklich brauchen würde (und ich meine nicht, es Anfängern leichter zu machen).

Stevie 22. Mai 2017 17:08

AW: Warum virtual / override bei destructor / constructor?
 
Zitat:

Zitat von HJay (Beitrag 1372350)
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...

Weil in aller Regel der Konstruktor mit Angabe der Klasse davor erstellt wird (TChild.Create oder TParent.Create). Dafür braucht er nicht virtual zu sein. Virtual muss er nur sein, wenn du über eine Metaklasse (TClass btw class of ...) den Konstruktor aufrufst (wie zuvor schon gezeigt). Der Destruktor wird aber auf der Instanzvariable aufgerufen, also muss ein virtual dispatch ausgeführt werden, damit auch der richtige Destruktor läuft (auch schon oben erklärt).

himitsu 22. Mai 2017 18:12

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.

HJay 22. Mai 2017 18:57

AW: Warum virtual / override bei destructor / constructor?
 
OK, vielen Dank an alle für Eure Erklärungen!

freimatz 23. Mai 2017 12:33

AW: Warum virtual / override bei destructor / constructor?
 
Bei uns in der Firma ist es Pflicht constructoren virtuell zu machen mit Verweis auf http://www.delphipraxis.net/668250-post12.html
(reintroduce ist schon noch erlaubt.)

Alallart 14. Jan 2023 13:51

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.

himitsu 14. Jan 2023 14:54

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