Virtuelle Constructoren
Virtuelle Constructoren
Hallo zusammen,
wie und wann ist es sinnvoll, einen Contructor als Virtual zu kennzeichnen?
Ich arbeite mit der Lazarusversion: 2.2.4
Ich habe mit mehreren, aufeinander aufbauenden Klassen gearbeitet.
Zunächst habe ich den Constructor der UR Vater Klasse, als Virtual gekennzeichnet. Danach gab es Probleme mit Override wenn der Constructor der Child Klasse andere Parameter hatte. Also habe ich diesen dann als Overload gekennzeichnet.
Die Konsequenz war, das er in den Overload Constructor nicht hinein gesprungen ist, sondern in den Vorgänger des Overload Constructor. Dies führte jedoch (verständlicher weise ) laufend zu Querschläger Fehler.
Also habe ich die Kennzeichnung für Virtual, Override und Overload bei den Constructoren entfernt.
Nun habe ich jedoch immer noch noch das Problem, das bei dem Aufruf von "INHERITED Create( Parameter )" zunächst in eine
"x-beliebige" Methode einer anderen Klasse gesprungen wird, welche nicht einmal in der USES Liste der Unit eingebunden ist, in welcher die zu erstellenden Klasse liegen. Erst danach, also im nächsten Step (F7), wird dann in die gewünschte INHERITED Methode (Create) gesprungen.
Irgendwo verschluckt bzw. vertauscht Lazarus die Einsprung Adressen in diese Methoden und ich kann nicht nachvollziehen was ich falsch gemacht habe.
Hat jemand von euch auch schon mal so ein Problem gehabt?
Kann es sein das es eine Mengenbegrenzung der eingebundenen Units gibt?
Kann jemand helfen?
Viele Grüße
Lorca
wie und wann ist es sinnvoll, einen Contructor als Virtual zu kennzeichnen?
Ich arbeite mit der Lazarusversion: 2.2.4
Ich habe mit mehreren, aufeinander aufbauenden Klassen gearbeitet.
Zunächst habe ich den Constructor der UR Vater Klasse, als Virtual gekennzeichnet. Danach gab es Probleme mit Override wenn der Constructor der Child Klasse andere Parameter hatte. Also habe ich diesen dann als Overload gekennzeichnet.
Die Konsequenz war, das er in den Overload Constructor nicht hinein gesprungen ist, sondern in den Vorgänger des Overload Constructor. Dies führte jedoch (verständlicher weise ) laufend zu Querschläger Fehler.
Also habe ich die Kennzeichnung für Virtual, Override und Overload bei den Constructoren entfernt.
Nun habe ich jedoch immer noch noch das Problem, das bei dem Aufruf von "INHERITED Create( Parameter )" zunächst in eine
"x-beliebige" Methode einer anderen Klasse gesprungen wird, welche nicht einmal in der USES Liste der Unit eingebunden ist, in welcher die zu erstellenden Klasse liegen. Erst danach, also im nächsten Step (F7), wird dann in die gewünschte INHERITED Methode (Create) gesprungen.
Irgendwo verschluckt bzw. vertauscht Lazarus die Einsprung Adressen in diese Methoden und ich kann nicht nachvollziehen was ich falsch gemacht habe.
Hat jemand von euch auch schon mal so ein Problem gehabt?
Kann es sein das es eine Mengenbegrenzung der eingebundenen Units gibt?
Kann jemand helfen?
Viele Grüße
Lorca
- af0815
- Lazarusforum e. V.
- Beiträge: 5890
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Niederösterreich
- Kontaktdaten:
Re: Virtuelle Constructoren
Bei so sonderbaren Problemen immer zuerst einmal alles Clean compilieren, bzw. den lib Ordner des Projektes löschen, damit ist einmal sichergestellt da alles neu und sauber kompiliert wird. Wenn es so aussieht, als würde er sich verschlucken, so ist es immer gut, wenn man mal alles sauber rekompiliert BEVOR man was herumbastelt.
Das mit dem virtual, overload und inherited hat bei dir schon grundlegend gestimmt. Da sehe ich keinen Fehler. Aber mit dem Debugger kommt man relativ rasch ans Ziel oder mit den LazLogger.
Das mit dem virtual, overload und inherited hat bei dir schon grundlegend gestimmt. Da sehe ich keinen Fehler. Aber mit dem Debugger kommt man relativ rasch ans Ziel oder mit den LazLogger.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
- fliegermichl
- Lazarusforum e. V.
- Beiträge: 1231
- Registriert: Do 9. Jun 2011, 09:42
- OS, Lazarus, FPC: Lazarus Fixes FPC Stable
- CPU-Target: 32/64Bit
- Wohnort: Echzell
Re: Virtuelle Constructoren
Bei virtuellen Methoden müssen die Parameter immer identisch sein. Man kann das aber durch reintroduce überschreiben.
Damit kannst du auch in dem constructor TDerived.Create inherited Create(Param1) aufrufen.
Code: Alles auswählen
type
TBase = class
constructor Create(Param1 : integer); virtual;
end;
TDerived = class ( TBase )
constructor Create(Param1 : integer; Param2 : string); reintroduce; virtual;
end;
- m.fuchs
- Lazarusforum e. V.
- Beiträge: 2550
- Registriert: Fr 22. Sep 2006, 19:32
- OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
- CPU-Target: x86, x64, arm
- Wohnort: Berlin
- Kontaktdaten:
Re: Virtuelle Constructoren
Es wäre auch gut, wenn du ein bisschen schildern könntest was eigentlich dein Zweck ist. In den meisten Fällen müssen Konstruktoren nicht virtual sein.
Wenn du overload benutzt ist das ein Zeichen dafür, dass der Konstruktor nicht virtual sein sollte.
Wie rufst du denn das .Create deiner abgeleiteten Klasse auf? Kannst du Code mitliefern für dein Problem?
Wenn du overload benutzt ist das ein Zeichen dafür, dass der Konstruktor nicht virtual sein sollte.
Wie rufst du denn das .Create deiner abgeleiteten Klasse auf? Kannst du Code mitliefern für dein Problem?
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
Re: Virtuelle Constructoren
Hallo af0815,
zunächst Danke für Deine Antwort
Nun, das mit dem "alles Clean compilieren, " kann ich nicht machen, da ich nicht weiß wo ich diesen Befehl ausführen kann.
Ich habe bereits mehrfach alles mit Start/Aufräumen und Compilieren und zuvor die Option: Löschen betätigt und danach mittels dem Schalter "Aufräumen und Compilieren" alles neu erstellt. Der Fehler bleibt jedoch.
Dann bin ich hergegangen und habe zuerst den LIB Ordner des Projektes mittels Windows Explorer gelöscht und dann erst "Start/Aufräumen und Compilieren" ausgeführt.
Das Problem bleibt nach wie vor.
Erst wenn ich das Projekt ganz neu anlege und alle Units und Klassen neu mache klappt es wieder.
Bis dann irgendwann wieder ein solcher Fehler in einer anderen Klasse auftaucht.
Ich habe bereits mehr als 10 mal dieses Projekt neu aufgebaut. Irgendwann tritt dann immer dieser Fehler auf.
Da ich nicht weiß, wo genau mein Fehler liegt, bzw. was schief läuft, bleibt mir keine andere Wahl. Doch dies geht mir jetzt echt auf den Keks.
Ich kann nicht einmal sage ob es ein Lazarus Fehler ist, oder mein eigener.
So langsam verliere ich echt die Lust, da ich mit dem Projekt einfach nicht weiter komme.
Viele Grüße
Lorca
zunächst Danke für Deine Antwort

Nun, das mit dem "alles Clean compilieren, " kann ich nicht machen, da ich nicht weiß wo ich diesen Befehl ausführen kann.
Ich habe bereits mehrfach alles mit Start/Aufräumen und Compilieren und zuvor die Option: Löschen betätigt und danach mittels dem Schalter "Aufräumen und Compilieren" alles neu erstellt. Der Fehler bleibt jedoch.
Dann bin ich hergegangen und habe zuerst den LIB Ordner des Projektes mittels Windows Explorer gelöscht und dann erst "Start/Aufräumen und Compilieren" ausgeführt.
Das Problem bleibt nach wie vor.
Erst wenn ich das Projekt ganz neu anlege und alle Units und Klassen neu mache klappt es wieder.
Bis dann irgendwann wieder ein solcher Fehler in einer anderen Klasse auftaucht.
Ich habe bereits mehr als 10 mal dieses Projekt neu aufgebaut. Irgendwann tritt dann immer dieser Fehler auf.
Da ich nicht weiß, wo genau mein Fehler liegt, bzw. was schief läuft, bleibt mir keine andere Wahl. Doch dies geht mir jetzt echt auf den Keks.
Ich kann nicht einmal sage ob es ein Lazarus Fehler ist, oder mein eigener.
So langsam verliere ich echt die Lust, da ich mit dem Projekt einfach nicht weiter komme.
Viele Grüße
Lorca
- af0815
- Lazarusforum e. V.
- Beiträge: 5890
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Niederösterreich
- Kontaktdaten:
Re: Virtuelle Constructoren
Wenn das so ist, so machst du immer dasselbe falsch.Lorca hat geschrieben: ↑Di 14. Feb 2023, 12:18Ich habe bereits mehr als 10 mal dieses Projekt neu aufgebaut. Irgendwann tritt dann immer dieser Fehler auf.
Da ich nicht weiß, wo genau mein Fehler liegt, bzw. was schief läuft, bleibt mir keine andere Wahl. Doch dies geht mir jetzt echt auf den Keks.
Ich kann nicht einmal sage ob es ein Lazarus Fehler ist, oder mein eigener.
So langsam verliere ich echt die Lust, da ich mit dem Projekt einfach nicht weiter komme.
* ) Namensgleichheit im Projekt unbedingt vermeiden
* ) Unitnamen und Objekte müssen sich unterscheiden
* ) Erzeugst du das richtige Objekt, oder machst du irgendwo einen TypeCast ?
Bau das Projekt man neu auf und schau dir genau an, wann dieser Fehler auftritt. Oder stell das Projekt zur Verfügung und erkläre was deiner Meinung nach schiefläuft.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
Re: Virtuelle Constructoren
Hallo zusammen
,
das mit reintroduce habe ich noch nie betrachtet. Das war wohl ein Fehler. Denn diesen Befehl kenne ich nicht.
Werde ich jedoch ausprobieren.
Für dieses Beispiel ist anzumerken, das ich für jede Klasse eine eigene Unit habe welche ich dann auch über die USES Liste einbinde.
Viele Grüße
Lorca
Ein code Beispiel;
TCL_Core = CLASS( TObject )
PUBLIC
CONSTRUCTOR Create; Virtual; (in der aktuellen Version keine Kennzeichnung als Virtual )
END;
TCL_Base = CLASS( TCL_Core )
PUBLIC
CONSTRUCTOR Create( iv_Value : Boolean ); OverLoad; (in der aktuellen Version keine Kennzeichnung als Overload )
END;
IMPLEMENTATION
CONSTRUCTOR TCL_Core.Create;
BEGIN
INHERITED Create;
END;
CONSTRUCTOR TCL_Base.Create( iv_Value : Boolean );
BEGIN
INHERITED Create;
END;

das mit reintroduce habe ich noch nie betrachtet. Das war wohl ein Fehler. Denn diesen Befehl kenne ich nicht.
Werde ich jedoch ausprobieren.
Für dieses Beispiel ist anzumerken, das ich für jede Klasse eine eigene Unit habe welche ich dann auch über die USES Liste einbinde.
Viele Grüße
Lorca
Ein code Beispiel;
TCL_Core = CLASS( TObject )
PUBLIC
CONSTRUCTOR Create; Virtual; (in der aktuellen Version keine Kennzeichnung als Virtual )
END;
TCL_Base = CLASS( TCL_Core )
PUBLIC
CONSTRUCTOR Create( iv_Value : Boolean ); OverLoad; (in der aktuellen Version keine Kennzeichnung als Overload )
END;
IMPLEMENTATION
CONSTRUCTOR TCL_Core.Create;
BEGIN
INHERITED Create;
END;
CONSTRUCTOR TCL_Base.Create( iv_Value : Boolean );
BEGIN
INHERITED Create;
END;
- m.fuchs
- Lazarusforum e. V.
- Beiträge: 2550
- Registriert: Fr 22. Sep 2006, 19:32
- OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
- CPU-Target: x86, x64, arm
- Wohnort: Berlin
- Kontaktdaten:
Re: Virtuelle Constructoren
Dein Code sagt mir noch nicht, wie du dein Create aufrufst.
Ich habe mal ein Beispiel entworfen, dass die Probleme bei virtuellen Konstruktoren aufzeigt:
Beim Aufruf bekommst du dann diese Ausgabe:
Zeigt: Wenn du virtuelle Konstruktoren benutzt, dann darfst du die Parameter nicht ändern. Sonst geht die die Virtualität verloren. Ist ja auch logisch, der Compiler muss ja mit der Klassendefinition von TFirst arbeiten - welche abgeleitete Klasse später benutzt wird weiß er ja nicht.
Ich habe mal ein Beispiel entworfen, dass die Probleme bei virtuellen Konstruktoren aufzeigt:
Code: Alles auswählen
program Project1;
{$MODE ObjFpc}
{$H+}
uses
Classes;
type
TFirst = class(TObject)
public
constructor Create; virtual;
end;
TFirstClass = class of TFirst;
TSecond = class(TFirst)
public
constructor Create; override;
end;
TThird = class(TSecond)
public
constructor Create(AInt: Integer); overload;
end;
TThirdAlt = class(TSecond)
public
constructor Create(AInt: Integer); reintroduce;
end;
constructor TFirst.Create;
begin
WriteLn('1');
end;
constructor TSecond.Create;
begin
WriteLn('2');
inherited Create;
end;
constructor TThird.Create(AInt: Integer);
begin
WriteLn('3');
inherited Create;
end;
constructor TThirdAlt.Create(AInt: Integer);
begin
WriteLn('3');
inherited Create;
end;
var
co: TFirstClass;
o: TFirst;
begin
co := TThird;
o := co.Create;
o.Free;
WriteLn;
co := TThirdAlt;
o := co.Create;
o.Free;
WriteLn;
o := TThirdAlt.Create(0);
o.Free;
end.
Code: Alles auswählen
2
1
2
1
3
2
1
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
Re: Virtuelle Constructoren
Hi 
Unitnamen fangen bei mir immer mit U gefolgt vom Projekt Kürzel an.
Klassennamen fangen bei mir immer mit TCL_ an.
Dadurch ist bei mir gewährleistet, das es keine Namensgleichheit gibt.
In meinen Programmen erzeuge ich IMMER ein echtes Objekt. (xx := TCL_nnn.Create)
Ein TypeCast findet auf existierende Objekte statt. z.B. TCL_xx( Sender ).yyy oder
SENDER as TCL_xx.
Gruß
Lorca

Unitnamen fangen bei mir immer mit U gefolgt vom Projekt Kürzel an.
Klassennamen fangen bei mir immer mit TCL_ an.
Dadurch ist bei mir gewährleistet, das es keine Namensgleichheit gibt.
In meinen Programmen erzeuge ich IMMER ein echtes Objekt. (xx := TCL_nnn.Create)
Ein TypeCast findet auf existierende Objekte statt. z.B. TCL_xx( Sender ).yyy oder
SENDER as TCL_xx.
Gruß
Lorca
Re: Virtuelle Constructoren
Hi,
meine Klasse rufe ich dann folgendermaßen auf:
VAR go_Base : TCL_Base;
BEGIN
go_base := TCL_Base.Create( True );
...
...
FreeAndNil( go_base );
END.
Gruß
Lorca
meine Klasse rufe ich dann folgendermaßen auf:
VAR go_Base : TCL_Base;
BEGIN
go_base := TCL_Base.Create( True );
...
...
FreeAndNil( go_base );
END.
Gruß
Lorca
- af0815
- Lazarusforum e. V.
- Beiträge: 5890
- Registriert: So 7. Jan 2007, 10:20
- OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
- CPU-Target: 32Bit (64Bit)
- Wohnort: Niederösterreich
- Kontaktdaten:
Re: Virtuelle Constructoren
Der Konstruktor muss auch wieder virtual sein (oder gar keinen modifier), da er eine andere Signatur als der geerbte hat, das ist für den Compiler ein neuer Konstruktor. Die Klasse TCL_Base hat jetzt 2 Konstruktoren, den von dir hier definierten und den geerbten.
Genaugenommen wäre es besser, wenn der mit der anderen Signatur überhaupt einen anderen Namen (zB CreateIV) bekommt
Schau dir zum Beispiel an wie es in den Vererbungen von TForm gehandhabt wird (kann man leicht bis zu TObject rückverfolgen). Da gibt es auch einige zusätzliche Construktoren zu beobachten.
Genaugenommen wäre es besser, wenn der mit der anderen Signatur überhaupt einen anderen Namen (zB CreateIV) bekommt
Code: Alles auswählen
TCL_Base = CLASS( TCL_Core )
PUBLIC
CONSTRUCTOR Create( iv_Value : Boolean );
END;
....
// Beispiel
CONSTRUCTOR Create( iv_Value : Boolean );
begin
iv:= iv_Value;
create;
end;
// oder
CONSTRUCTOR Create( iv_Value : Boolean );
begin
create;
iv:= iv_Value;
end;
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).
- m.fuchs
- Lazarusforum e. V.
- Beiträge: 2550
- Registriert: Fr 22. Sep 2006, 19:32
- OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
- CPU-Target: x86, x64, arm
- Wohnort: Berlin
- Kontaktdaten:
Re: Virtuelle Constructoren
Schmeiß mal bitte überall virtual raus bei deinen Konstruktoren. Das brauchst du doch gar nicht.Lorca hat geschrieben: ↑Di 14. Feb 2023, 12:48meine Klasse rufe ich dann folgendermaßen auf:
Code: Alles auswählen
VAR go_Base : TCL_Base; BEGIN go_base := TCL_Base.Create( True ); ... ... FreeAndNil( go_base ); END.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de
-
- Beiträge: 1752
- Registriert: Di 23. Sep 2014, 17:46
- OS, Lazarus, FPC: Win10 | Linux
- CPU-Target: x86_64
Re: Virtuelle Constructoren
Kurz zur erklärung, was bedeutet Virtual.
Wenn eine Methode als Virtual gekennzeichnet ist bedeutet das das diese nicht statisch durch den Compiler ermittelt wird, sondern dynamisch über die so genannte V-Table des objekts.
Am besten kann man das erklären wenn man OOP einfach mal mit records nachbaut. Nehmen wir diese simple klasse:
Im grunde ist das nix anderes als das folgende mit Records:
In diesem Fall ist die klasse nix anderes als syntaktischer Zucker der aus variable.method den funktionsbezeichner TYPE(variable)__method macht. Welche funktion aufgerufen wird hängt also von dem typen der variable ab.
Eine Virtuelle Methode hingegen ist im Grunde nichts anderes als ein Funktionspointer der im Objekt selbst lebt:
Ist also so ungefähr so etwas:
Die analogie ist nicht ganz perfekt, da die V-Table normalerweise nicht im Objekt selbst liegt, sondern separat, wobei das objekt nur einen Pointer auf die V-Table hat, aber im grunde funktioniert es genau so. Bei einer virtuellen methode liegt ein Funktionspointer in dem Objekt selbst. Dann entscheidet der Compiler nicht welche funktion aufgerufen werden soll an hand des typen der variable zur kompilezeit, sondern zur Laufzeit wird der aktuelle Funktionspointer aus der V-Table geladen und ausgeführt.
Das hat z.B. auch den Zusatzeffekt das man die V-Table eines Objektes im nachhinein verändern kann. Man kann also ein Objekt erstellen, sodass PrintInt die standard funktionalität ausführt, und dann später die VTable ändern sodass dann eine andere Funktion statdessen ausgeführt wird.
Beispiel:
In diesem fall wird immer die Print Methode ausgeführt von dem typen der vor dem . steht, also bei "f" dann TFoo.Print weil f vom typen TFoo ist, und bei TBar(f) dann TBar, da f hier zu TBar gecastet wird.
Wäre print jetzt virtual (und override in TBar), dann würde in der V-Table von f ein Funktionspointer stecken der entweder auf TFoo__Print zeigt, falls TFor.Create aufgerufen wurde, oder auf TBar__Print falls TBar.Create verwendet wurde um das Objekt zu erstellen, da der Konstruktor diese V-Table setzt.
Damit das alles funktionieren kann, muss der funktionspointer immer von dem gleichen typen sein. Das folgende geht offensichtlich nicht:
Da test ein Funktionspointer auf eine Funktion ist die einen Parameter vom typen Integer erhält kann man keine Funktion referenzieren die 2 parameter von unterschiedlichen Typen bekommt. Es ist einfach der falsche typ.
Genau das gleiche ist bei der V-Table, ein funktionspointer kann nur in die V-Table geschrieben werden wenn sie den selben typen hat wie in der V-Table definiert wurde. Virtual definiert den typen in der V-Table, und override schreibt eine andere funktion in einen bestehenden eintrag in die V-Table. Und damit das geht, müssen beide vom selben typen sein, genau wie im beispiel oben.
Von daher wenn du eine Methode virtual definierst, können alle Methoden die diese überschreiben, und damit die Variable in der V-Table überschreiben, den selben Typen, also die selbe Funktionssignatur haben.
Jetzt zu deiner Ursprünglichen Frage, warum einen Konstruktor virtuell machen? In der typischen Logik die oben erklärt wurde macht das keinen Sinn, wenn die VTable teil des Objekts ist, das vom Konstruktor erstellt wird, dann macht es ja keinen Sinn den konstruktor in diese Tabelle zu schreiben nachdem er bereits verwendet wurde.
Der Grund hierfür ist das Delphi (und damit auch ObjectPascal) aus der JavaEsquen OOP nieche kommen, in der Klassen selbst Objekte von Metaklassen sind. Diese können selbst methoden und felder haben:
Hier ist TTest selbst ein objekt von der Metaklasse "class of TTest":
Und diese Metaklassen spiegeln auch Vererbung wieder:
Dies ist das exakt gleiche beispiel wie oben, nur mit Klassen statt objekten und Metaklassen statt klassen. Aber im grunde ist es nix anderes.
Und genau wie bei normalen Klassen kann hier auch virtual und override verwendet werden:
Und der Konstruktor ist nichts anderes als eine Klassenmethode die ein paar zusatzfunktionalitäten hat. D.h. Virtuelle Konstruktoren erlauben so etwas:
Es erlaubt also das dynamische Klassenerstellen zur Laufzeit indem man die Klassen selbst als variablen benutzt.
Und genau wie bei normalen Virtuellen Methoden muss auch hier der Typ, also die Signatur zwischen virtual und override gleich sein.
Die letzt Frage ist also nur noch, brauchst du virtuelle Konstruktoren. Die kurze antwort ist, da du nachfragen musstest was sie machen, benutzt du keine Metaklassen in deinem code. Also kannst du getrost auf Virtuelle Konstruktoren verzichten.
Generell muss man sich virtuelle Methoden immer überlegen. Virtuelle Methoden benötigt man nur, wenn man OOP polymorphismus machen möchte. Aber Virtuelle Methoden haben einen nicht zu unterschätzende Auswirkung auf deinen Code. Zum einen musst du wenn du die Signatur ändern willst dich immer mit Reintroduce rumkämpfen um Meldungen zu unterdrücken, zum anderen Kann der Kompiler weniger optimieren (da z.B. inlining nicht möglich ist mit virtuellen Methoden).
Von daher die Faustregel: Benutz virtual nur wenn dus auch wirklich brauchst. Im nachinein hinzufügen kann man es immer noch. (Ein bisschen anders verhält es sich bei Bibliotheken, ich hab schon mehr als einmal geflucht weil z.B. TThread.Start nicht virtuell ist und damit nicht überladen werden kann)
Wenn eine Methode als Virtual gekennzeichnet ist bedeutet das das diese nicht statisch durch den Compiler ermittelt wird, sondern dynamisch über die so genannte V-Table des objekts.
Am besten kann man das erklären wenn man OOP einfach mal mit records nachbaut. Nehmen wir diese simple klasse:
Code: Alles auswählen
TTest = class
public
FMyInt: Integer;
procedure PrintInt;
end;
procedure TTest.PrintInt;
begin
WriteLn(Self.FMyInt);
end;
...
var test: TTest;
...
test.PrintInt;
Code: Alles auswählen
TTest = record
FMyInt: Integer;
end;
procedure TTest__PrintInt(var self: TTest);
begin
WriteLn(Self.FMyInt);
end;
...
var test: PTest;
...
TTest__PrintInt(test^);
Eine Virtuelle Methode hingegen ist im Grunde nichts anderes als ein Funktionspointer der im Objekt selbst lebt:
Code: Alles auswählen
TTest = class
public
FMyInt: Integer;
procedure PrintInt; virtual;
constructor Create(AInt: Integer);
end;
Code: Alles auswählen
TTest = record
public
FMyInt: Integer;
PrintInt = procedure;
end;
procedure TTest__PrintInt(var self: TTest);
begin
WriteLn(self.FMyInt);
end;
procedure TTest__Create(out self: TTest); // Konstruktor equivalent
begin
// Automatisch generierter Konstruktor teil:
// Baue V-Table auf
self.PrintInt := @TTest__PrintInt;
// Rest des konstruktors
end;
Das hat z.B. auch den Zusatzeffekt das man die V-Table eines Objektes im nachhinein verändern kann. Man kann also ein Objekt erstellen, sodass PrintInt die standard funktionalität ausführt, und dann später die VTable ändern sodass dann eine andere Funktion statdessen ausgeführt wird.
Beispiel:
Code: Alles auswählen
TFoo = class
public procedure Print;
end;
TBar = class(TFoo)
public procedure Print;
end;
procedure TFoo.Print;
begin
WriteLn('Foo');
end;
procedure TBar.Print;
begin
WriteLn('Bar');
end;
var
f: TFoo;
begin
f := TBar.Create;
f.Print; // "Foo"
TBar(f).Print; // "Bar"
end;
Wäre print jetzt virtual (und override in TBar), dann würde in der V-Table von f ein Funktionspointer stecken der entweder auf TFoo__Print zeigt, falls TFor.Create aufgerufen wurde, oder auf TBar__Print falls TBar.Create verwendet wurde um das Objekt zu erstellen, da der Konstruktor diese V-Table setzt.
Damit das alles funktionieren kann, muss der funktionspointer immer von dem gleichen typen sein. Das folgende geht offensichtlich nicht:
Code: Alles auswählen
procedure Test1(A: Integer);
...
procedure Test2(A: String; B: Integer);
...
var
test: procedure(A: Integer);
begin
test := @Test2;
Genau das gleiche ist bei der V-Table, ein funktionspointer kann nur in die V-Table geschrieben werden wenn sie den selben typen hat wie in der V-Table definiert wurde. Virtual definiert den typen in der V-Table, und override schreibt eine andere funktion in einen bestehenden eintrag in die V-Table. Und damit das geht, müssen beide vom selben typen sein, genau wie im beispiel oben.
Von daher wenn du eine Methode virtual definierst, können alle Methoden die diese überschreiben, und damit die Variable in der V-Table überschreiben, den selben Typen, also die selbe Funktionssignatur haben.
Jetzt zu deiner Ursprünglichen Frage, warum einen Konstruktor virtuell machen? In der typischen Logik die oben erklärt wurde macht das keinen Sinn, wenn die VTable teil des Objekts ist, das vom Konstruktor erstellt wird, dann macht es ja keinen Sinn den konstruktor in diese Tabelle zu schreiben nachdem er bereits verwendet wurde.
Der Grund hierfür ist das Delphi (und damit auch ObjectPascal) aus der JavaEsquen OOP nieche kommen, in der Klassen selbst Objekte von Metaklassen sind. Diese können selbst methoden und felder haben:
Code: Alles auswählen
TTest = class
public
class var MyInt: Integer;
class procedure PrintInt;
end;
class procedure TTest.PrintInt;
begin
WriteLn(Self.MyInt);
end;
begin
TTest.PrintInt;
Code: Alles auswählen
TTest = class
public
class var MyInt: Integer;
class procedure PrintInt;
end;
TTestClass = class of TTest;
...
var
t: TTestClass;
begin
t := TTest;
t.PrintInt;
Code: Alles auswählen
TFoo = class
public class procedure Print;
end;
TFooClass = class of TFoo;
TBar = class(TFoo)
public class procedure Print;
end;
TBarClass = class of TBar;
class procedure TFoo.Print;
begin
WriteLn('Foo');
end;
class procedure TBar.Print;
begin
WriteLn('Bar');
end;
var
f: TFooClass;
begin
f := TBar;
f.Print; // "Foo"
TBarClass(f).Print; // "Bar"
Und genau wie bei normalen Klassen kann hier auch virtual und override verwendet werden:
Code: Alles auswählen
TFoo = class
public class procedure Print; virtual;
end;
TFooClass = class of TFoo;
TBar = class(TFoo)
public class procedure Print; override;
end;
TBarClass = class of TBar;
class procedure TFoo.Print;
begin
WriteLn('Foo');
end;
class procedure TBar.Print;
begin
WriteLn('Bar');
end;
var
f: TFooClass;
begin
f := TBar;
f.Print; // "Bar" weil virtual den Funktionspointer zu TBarClass__Print überschrieben hat
TBarClass(f).Print; // "Bar"
Code: Alles auswählen
TFoo = class
public
Name: String;
constructor Create; virtual;
end;
TFooClass = class of TFoo;
TBar = class(TFoo)
public
constructor Create; override;
end;
constructor TBar.Create;
begin
inherited Create;
Name := 'Bar';
end;
constructor TFoo.Create;
begin
Name := 'Foo';
end;
function CreateObject(objClass: TFooClass): TFoo;
begin
Result := objClass.Create;
end;
var
f: TFoo;
begin
f := CreateObject(TFoo);
WriteLn(f.Name); // Foo
f := CreateObject(TBar);
WriteLn(f.Name); // Bar
Und genau wie bei normalen Virtuellen Methoden muss auch hier der Typ, also die Signatur zwischen virtual und override gleich sein.
Die letzt Frage ist also nur noch, brauchst du virtuelle Konstruktoren. Die kurze antwort ist, da du nachfragen musstest was sie machen, benutzt du keine Metaklassen in deinem code. Also kannst du getrost auf Virtuelle Konstruktoren verzichten.
Generell muss man sich virtuelle Methoden immer überlegen. Virtuelle Methoden benötigt man nur, wenn man OOP polymorphismus machen möchte. Aber Virtuelle Methoden haben einen nicht zu unterschätzende Auswirkung auf deinen Code. Zum einen musst du wenn du die Signatur ändern willst dich immer mit Reintroduce rumkämpfen um Meldungen zu unterdrücken, zum anderen Kann der Kompiler weniger optimieren (da z.B. inlining nicht möglich ist mit virtuellen Methoden).
Von daher die Faustregel: Benutz virtual nur wenn dus auch wirklich brauchst. Im nachinein hinzufügen kann man es immer noch. (Ein bisschen anders verhält es sich bei Bibliotheken, ich hab schon mehr als einmal geflucht weil z.B. TThread.Start nicht virtuell ist und damit nicht überladen werden kann)
Re: Virtuelle Constructoren
Hallo zusammen,
ein ganz herzlichen Dank für eure Mühen und Ausführungen.
Viele Grüße
Lorca
ein ganz herzlichen Dank für eure Mühen und Ausführungen.

Viele Grüße
Lorca