Autor |
Beitrag |
Patrick Bauschat
Hält's aus hier
Beiträge: 6
Erhaltene Danke: 1
|
Verfasst: Mo 05.11.12 01:20
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
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: Mo 05.11.12 01:46
Moin und  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!
Der Konstruktor ist ein ganz spezieller Sonderfall, da gelten besondere Regeln.
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
Martok
      
Beiträge: 3661
Erhaltene Danke: 604
Win 8.1, Win 10 x64
Pascal: Lazarus Snapshot, Delphi 7,2007; PHP, JS: WebStorm
|
Verfasst: 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.
_________________ "The phoenix's price isn't inevitable. It's not part of some deep balance built into the universe. It's just the parts of the game where you haven't figured out yet how to cheat."
Für diesen Beitrag haben gedankt: BenBE, Narses
|
|
jaenicke
      
Beiträge: 19315
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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 
Hält's aus hier
Beiträge: 6
Erhaltene Danke: 1
|
Verfasst: Mo 05.11.12 17:43
Vielleicht verstehe ich nur nicht was virtual macht
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?
|
|
WasWeißDennIch
      
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: Mo 05.11.12 17:54
Vielleicht hilft Dir das hier zum Verständnis, besonders das Beispiel am Ende des Beitrags: forum.delphi-treff.d...iewfull=1#post231715
|
|
jaenicke
      
Beiträge: 19315
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mo 05.11.12 18:04
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
      
Beiträge: 1248
Erhaltene Danke: 187
XP - Server 2008R2
D2 - Delphi XE
|
Verfasst: Mo 05.11.12 18:07
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. |
_________________ Das Problem liegt üblicherweise zwischen den Ohren H₂♂
DRY DRY KISS
|
|
Patrick Bauschat 
Hält's aus hier
Beiträge: 6
Erhaltene Danke: 1
|
Verfasst: 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.
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.
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
      
Beiträge: 19315
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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 
Hält's aus hier
Beiträge: 6
Erhaltene Danke: 1
|
Verfasst: 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
      
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: 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.
Zuletzt bearbeitet von WasWeißDennIch am Di 06.11.12 14:47, insgesamt 1-mal bearbeitet
Für diesen Beitrag haben gedankt: BenBE
|
|
jaenicke
      
Beiträge: 19315
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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 
Hält's aus hier
Beiträge: 6
Erhaltene Danke: 1
|
Verfasst: 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
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.
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; |
|
|
|