Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Warum ist Destroy virtual?
Patrick Bauschat - Mo 05.11.12 01:20
Titel: Warum ist Destroy virtual?
Guten Abend,
ich gehe momentan ein Delphi Buch von Anfang bis Ende durch und bin gerade bei Klassen. Dort steht, dass Destroy immer mit dem Schlüsselword override versehen werden muss, da Destroy eine virtuelle Methode ist. Es steht aber nicht warum. Create ist keine virtuelle Methode. Was ist also der Unterschied weshalb Destroy virtuell und Create nicht ist. In beiden wird inherited aufgreufen.
Gruß
Patrick
Narses - Mo 05.11.12 01:46
Moin und :welcome: in der EE!
Patrick Bauschat hat folgendes geschrieben : |
Dort steht, dass Destroy immer mit dem Schlüsselword override versehen werden muss, da Destroy eine virtuelle Methode ist. |
Andersrum wird ein Schuh draus: da du normalerweise Destroy überschreiben willst, ist es eine virtuelle Methode - sonst könnte man sie nicht überschreiben! :idea:
Der Konstruktor ist ein ganz spezieller Sonderfall, da gelten besondere Regeln.
cu
Narses
Martok - Mo 05.11.12 03:08
Narses hat folgendes geschrieben : |
Der Konstruktor ist ein ganz spezieller Sonderfall, da gelten besondere Regeln. |
Naja, nur bedingt. Im Grunde genommen ist das auch nur eine
class function, die ein Objekt der Klasse per NewInstance(), InitInstance erzeugt, initialisiert und zurückgibt. Das ganze noch mit Indirection durch _ClassCreate(), was ein Mini-Try-Except baut, um Exceptions im Konstruktor abzufangen.
Und da ist auch genau der Unterschied.
Der Effekt von
virtual ist der, dass immer die Methode der echten Klasse des Objekts aufgerufen wird, während normalerweise die Version aus der Deklaration der Variable aufgerufen wird. Beispiel:
Beispielcode
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| type TKlasseA = class constructor Create; destructor Destroy; override; procedure Test; end;
TKlasseB = class(TKlasseA) constructor Create; destructor Destroy; override; procedure Test; end;
var Instanz: TKlasseA; begin Instanz:= TKlasseB.Create;
Instanz.Test;
Instanz.Free; |
Ohne
override und
virtual wird TKlasseA.Test aufgerufen, obwohl ein TKlasseB-Objekt erstellt wurde - eben weil das der Typ der Variablen ist. Kommentiert man das virtual/override wieder ein, wird tatsächlich TKlasseB.Test aufgerufen, da bei virtuellen Methoden immer geguckt wird, welcher Typ wirklich vorliegt.
So, und jetzt zu Create/Destroy.
Create soll ja immer eine Instanz der Klasse erstellen, die angegeben wurde. Klar - Wenn ich TKlasseA.Create schreibe, soll auch das passieren ;)
Destroy soll aber
das Objekt freigeben - in dem Beispiel oben wollen wir also TKlasseB.Destroy, obwohl die Variable vom Typ TKlasseA ist. Und genau das macht eben
virtual.
Falls du ein Delphi mit VCL-Quelltexten zur Verfügung hast - solche internen Geschichten findet man in der System.pas. Da ist z.B. TObject deklariert, was die Magie für Create und Free bereitstellt. In früheren Versionen (bis D7) stand sowas auch mal ausführlich in der Hilfe, ich bin mir nicht sicher, ob es diese Hintergrundartikel noch/wieder gibt.
Übrigens, zur abschließenden Verwirrung: Um von Klassen, die in einer Variable vom Typ
class of TBlubb gespeichert sind, Instanzen zu erstellen, muss der Konstruktor virtuell sein. Was wiederum komplett unintuitiv und nur der Behandlung von
class of geschuldet ist. Ist recht wichtig, wenn man eine Factory bauen will.
Und eins noch: inherited wird bei beiden Fällen aufgerufen, um die Methode der Vorfahrenklasse aufzurufen. Der Unterschied ist nur, wo diese "Kette" startet.
jaenicke - Mo 05.11.12 06:32
Und auch wichtig:
Bei älteren Delphiversionen konnte man inherited nicht "prophylaktisch" aufrufen. Wenn dann kein vererbtes Element da war, hat es geknallt. Bei aktuelleren Delphiversionen kann man es einfach hinschreiben um so Änderungen vorzubeugen, bei denen das Elternelement vielleicht mal doch einen Konstruktor bekommt.
Patrick Bauschat - Mo 05.11.12 17:43
Vielleicht verstehe ich nur nicht was virtual macht
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:
| type TA = class(TObject) function Text: String; end;
TB = class(TA) function Text: String; end;
TC = class(TB) function Text: String; end;
function TA.Text: String; begin Result := ClassName; end;
function TB.Text: String; begin Result := inherited Text; end;
function TC.Text: String; begin Result := inherited Text; end;
procedure TForm1.Button1Click(Sender: TObject); var A: TA; B: TB; C: TC; begin A := TA.Create; B := TB.Create; C := TC.Create; try ShowMessage(A.Text); ShowMessage(B.Text); ShowMessage(C.Text); finally A.Free; B.Free; C.Free; end; end; |
Ob ich es mit virtual mache oder ohne, ich greife immer auf die Basisklasse. Wo ist also der Unterschied?
jaenicke - Mo 05.11.12 18:04
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
| type TA = class(TObject) function Text: String; end;
TB = class(TA) function Text: String; end;
function TA.Text: String; begin Result := 'a'; end;
function TB.Text: String; begin Result := 'b'; end;
procedure TForm1.Button1Click(Sender: TObject); var A, B: TA; begin A := TA.Create; B := TB.Create; try ShowMessage(A.Text); ShowMessage(B.Text); finally A.Free; B.Free; end; end; |
Probier das einmal mit und ohne virtual / override aus. ;-)
bummi - Mo 05.11.12 18:07
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:
| unit Unit3;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TA = class(TObject) function Text: String; virtual; end;
TB = class(TA) function Text: String; override; end;
TC = class(TB) end;
TD = class(TA) function Text: String;reintroduce;virtual; end;
TE = class(TD) function Text: String;override; end;
TForm3 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private public end;
var Form3: TForm3;
implementation
{$R *.dfm}
function TA.Text: String; begin Result := ClassName ; end;
function TB.Text: String; begin Result := inherited Text + ' override B ' + ClassName; end;
procedure TForm3.Button1Click(Sender: TObject); var A: TA; B: TB; C: TC; D: TD; E: TE;
begin A := TA.Create; B := TB.Create; C := TC.Create; D := TD.Create; E := TE.Create;
try ShowMessage(A.Text); ShowMessage(B.Text); ShowMessage(C.Text); ShowMessage(D.Text); ShowMessage(E.Text); finally A.Free; B.Free; C.Free; D.Free; E.Free; end; end;
function TD.Text: String; begin Result := 'reintroduced' end;
function TE.Text: String; begin Result := inherited Text + ' override E ' + ClassName;
end;
end. |
Patrick Bauschat - Di 06.11.12 13:13
Nennt mich dumm, aber irgendwie vestehe ich es immer noch nicht. Ich sehe was ihr zeigen wollt und die Beispiele sind sehr interessant, aber was virtual angeht da fehlt der Geistesblitz. Die Beispiel sind sehr interessant und einiges neu, aber irgendwie erkenne ich noch nicht das Besondere von virtual.
Ich nutze das Beispiel von Jaenicke für etwas was mir aufgefallen ist. Das hier funktioniert ohne virtual und override problemlos. Ich überschreibe dabei die Funktion Text aus TA mit einer gleichnameigen Funktion,
allerdings mit Parametern.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
| type TA = class(TObject) function Text: String; end;
TB = class(TA) function Text(blablablabla: String): String; end;
function TA.Text: String; begin Result := 'a'; end;
function TB.Text(blablablabla: String): String; begin Result := blablablabla; end;
procedure TForm1.Button1Click(Sender: TObject); var A: TA; B: TB; begin A := TA.Create; B := TB.Create; try ShowMessage(A.Text); ShowMessage(B.Text('blablablabla')); finally A.Free; B.Free; end; end; |
Mit virtual und override funktioniert das oben nicht. In dem Fall müssen beide Funktionen in der Schreibweise übereinstimmen. Das ist bisher das einzig Besondere was mir aufgefallen ist.
Denn ob ich dieses Beispiel mit oder ohne virtual und override nehme, in beiden Fällen ist das Ergebnis gleich. In beiden Fällen kann ich mit inherited an den Vorgänger.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
| type TA = class(TObject) function Text: String; end;
TB = class(TA) function Text: String; end;
function TA.Text: String; begin Result := 'a'; end;
function TB.Text: String; begin Result := inherited Text; end;
procedure TForm1.Button1Click(Sender: TObject); var A: TA; B: TB; begin A := TA.Create; B := TB.Create; try ShowMessage(A.Text); ShowMessage(B.Text); finally A.Free; B.Free; end; end; |
Kann es also sein, dass das einzig besondere an virtual und override ist, dass man die Methoden gleich schreiben muss, während man sonst das ignortieren kann?
jaenicke - Di 06.11.12 13:19
Du packst deine Objekte auch immer in Variablen vom exakten Typ des erzeugten Objekts. Viel interessanter ist es aber in dem Zusammenhang so zu arbeiten wie ich in meinem Beispiel. Ich habe einfach ein Objekt vom Typ TA. Ob das nun konkret ein TA oder ein davon abgeleitetes vom Typ TB ist, interessiert mich nicht, ich weiß aber, dass die Funktion Text mit dem Rückgabewert String da ist.
Ich rufe das also auf ohne zu wissen welcher konkrete Typ in der Variablen steckt.
Zum Beispiel habe ich eine Variable vom Typ Auto. Ob das nun ein Mercedes oder ein LKW ist, interessiert mich nicht, wenn ich die Funktion Gasgegeben ausführen will. Die muss von den konkreten Typen aber durch Überschreiben implementiert sein (und dafür im allgemeinen Auto virtuell sein).
Patrick Bauschat - Di 06.11.12 13:55
Ich verstehe und glaub mir, ich hab mich mit deinem, wie auch anderen Beispielen stark auseinendergesetzt. Und auch wenn die Frage längst beantwortet sein wird, werde ich mich noch mit einigem draus beschäftigen, aber die Frage die mich hier vordringlich interessiert ist wieso Destroy virtual ist.
Wenn ich also schreibe, dass mich die Antworten verwirren, dann weil ich mich nach den Beispielen nicht sicher bin ob das die Hauptaussage von virtual ist. In deinem Beispiel ohne virtual und override kommt als Ergebnis das Ergebnis aus TA. Mit virtual und override liefert A das Ergebnis aus TA und B das Ergebnis aus TB. Das habe ich verstanden und das ist sehr interessant, nur verstehe ich nicht ob das die Hauptaussage von virtual ist oder nur eine Nebeneffekt. Denn auch wenn ich dein Beispiel schon irgendwo in meinem Buch gesehen habe, so bist bisher der einzige der virtual in diesem Zusammenhang bringt.
Ich hab seit dem einiges gegoogelt und jeder gibt eine kurze Aussage die aber irgendwie nichts aussagt. Da scheribt einer: "Methoden müssen mit dem Schlüsselwort virtual zumindest im protected-Abschnitt der Ursprungsklasse deklariert sein. So kann man sie in der neuen Klasse mit override überschreiben." Es geht aber auch ohne. Mein zweites Beispiel funktioniert mit und ohne virtual.
Meine kleine Erkenntnis oben würde zumindest zum Teil meiner Frage beantworten, denn Create gibt es mal mit, mal ohne Parameter, bei Destroy ist dagegen immer die Schriebweise gleich
WasWeißDennIch - Di 06.11.12 14:14
Wie Du in den Beispielen sehen konntest, werden die verdeckenden Methoden u.U. nicht aufgerufen, sondern nur die der Vorfahrklasse. Wenn also z.B. eine abgeleitete Klasse Speicher reserviert und im Destruktor wieder freigibt, so könnte das dazu führen, dass Speicherlecks entstehen, wenn der Destruktor nicht virtuell wäre.
jaenicke - Di 06.11.12 14:45
Der Unterschied bei virtuellen Methoden ist, dass sie nicht an einer festen Speicherstelle stehen (die dann beim Kompilieren einfach ersetzt wird), sondern dass diese in einer Tabelle in der Struktur der entsprechenden Klasse stehen. Beim Aufruf einer solchen Methode wird nun an der Stelle der VMT (Virtual Method Table) nachgeschaut und dann die entsprechende Adresse für den Aufruf benutzt.
In dieser Tabelle können dann Methoden mit override durch andere ersetzt (überschrieben) werden.
Patrick Bauschat - Di 06.11.12 19:28
Dein Beispiel ist nicht schlecht, aber man versteht es erst wenn man es versteht. Ich hab es etwas umgeschrieben. Es ist nicht besser, nur leicht anders
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:
| type TA = class(TObject) function Text: String; virtual; end;
TB = class(TA) function Text: String; override; end;
TC = class(TA) function Text: String; reintroduce; end;
function TA.Text: String; begin Result := 'a'; end;
function TB.Text: String; begin Result := 'b'; end;
function TC.Text: String; begin Result := 'c'; end;
procedure TForm1.Button1Click(Sender: TObject); var A: TA; B: TA; C: TA; begin A := TA.Create; B := TB.Create; C := TC.Create; try ShowMessage(A.Text); ShowMessage(B.Text); ShowMessage(C.Text); finally A.Free; B.Free; C.Free; end; end; |
Das Dilemma ist, dass ich durchaus sehe auf was du hinaus willst. Ein anderes Beispiel. Man könnte man es so deuten, dass die Methode einmal wirklich überschrieben ist, das andere mal nicht, denn wenn man über die Basisklasse geht, dann erhält man einmal das Ergebnis der überschriebenen Methode, das andere mal das der Basisklasse. Vielleicht sollte ich das Buch zu ende lesen, denn ich weiß immer noch nicht was es mir sagen soll.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
| type TA = class(TObject) function Text: String; virtual; end;
TB = class(TA) function Text: String; override; end;
TC = class(TA) function Text: String; reintroduce; end;
function TA.Text: String; begin Result := 'a'; end;
function TB.Text: String; begin Result := 'b'; end;
function TC.Text: String; begin Result := 'c'; end;
function Test(X: TA): String; begin Result := X.Text; end;
procedure TForm1.Button1Click(Sender: TObject); var A: TA; B: TB; C: TC; begin A := TA.Create; B := TB.Create; C := TC.Create; try ShowMessage(Test(A)); ShowMessage(Test(B)); ShowMessage(Test(C)); finally A.Free; B.Free; C.Free; end; end; |
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!