2016-12-30 1 views
2

Ich bin verwirrt: warum Aufruf eines Delphi-Konstruktor explizit/als eine normale Methode würde keine neue Instanz erstellen/warum kein Speicherlecks?Delphi-Konstruktor als gewöhnliche Methode aufrufen - irgendwelche versteckten Nebenwirkungen?

Hier ist ein Beispielcode:

TMyHelperClass = class(TObject) 
private 
    fSomeHelperInt: integer; 
public 
    property SomeHelperInt : integer read fSomeHelperInt write fSomeHelperInt; 
    constructor Create (const initSomeHelperInt : integer); 
end; 

TMyClass = class(TObject) 
private 
    fHelper : TMyHelperClass; 
public 
    constructor Create(const initSomeInt: integer); 
    destructor Destroy; override; 
    property Helper : TMyHelperClass read fHelper; 
end; 

Umsetzung:

constructor TMyHelperClass.Create(const initSomeHelperInt: integer); 
begin 
    fSomeHelperInt := initSomeHelperInt; 
end; 

constructor TMyClass.Create(const initSomeInt: integer); 
begin 
    fHelper := TMyHelperClass.Create(initSomeInt); 
end; 

destructor TMyClass.Destroy; 
begin 
    fHelper.Free; 
    inherited; 
end; 

Und Nutzung:

var 
    my : TMyClass; 
begin 
    my := TMyClass.Create(2016); 
    try 
    //WHY is this ok to be used ? 
    my.Helper.Create(2017); 
    finally 
    my.Free; 
    end; 
end; 

Wie komme ich erstellen Konstruktor als ein übliches Verfahren der TMyHelperClass + s nennen könnte ? Ich meine - das ist genau so, wie ich es will - aber wie kommt es zu keinen Problemen (mit Gedächtnis)?

Ich denke, die Antwort wird sein, weil die Create-Methode nicht wie TMyHelperClass.Create aufgerufen wurde (um eine Instanz von TMyHelperClass zu erstellen)?

Ist diese Art des Aufrufs des Konstruktors als eine gewöhnliche Methode akzeptabel/ok zu verwenden?

+2

Lesen Sie die Dokumentation. Es wird dort erklärt. Und du willst das nicht tun. Es ist wirklich eine schlechte Übung. Es bricht zusammen, sobald Ihre Klasse eine andere Ressource als ihren eigenen Speicher besitzt. Erstellen Sie eine separate öffentliche Methode, die vom Konstruktor aufgerufen wird. –

+0

Ah :(Hat gelesen, aber offensichtlich nicht sehr geduldig. Hier ist es: – Torpedo

+0

Wenn ein Konstruktor einen Objektverweis (statt einer Klassenreferenz) aufruft, erzeugt er kein Objekt, stattdessen arbeitet der Konstruktor mit dem angegebenen Objekt , führt nur die Anweisungen in der Konstruktorimplementierung aus und gibt dann einen Verweis auf das Objekt – Torpedo

Antwort

10

Ja, Sie können den Konstruktor als eine gewöhnliche Methode bezeichnen.
Es ist eine schlechte Praxis, dies zu tun.

Warum keine Speicherlecks?

Von: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Methods#Constructors

Wenn ein Konstruktor einen Objektverweis aufgerufen wird (anstatt eine Klassenreferenz), ist es nicht ein Objekt erstellen. Stattdessen arbeitet der -Konstruktor mit dem angegebenen Objekt und führt nur die -Anweisungen in der Implementierung des Konstruktors aus und gibt dann einen -Verweis auf das Objekt zurück. Ein Konstruktor wird in der Regel auf einer Objektreferenz in Verbindung mit dem reservierten Wort aufgerufen, das an geerbt wird, führen Sie einen geerbten Konstruktor aus.

Der Compiler unterschiedliche Code erzeugen, wenn TObject.Create(Klasse Referenz) vs AObject.Create(Beispiel Referenz) aufrufen.

Anti-Muster
Abusing den Konstruktor als normale Methode Warnung wird zu Problemen führen, wenn die Zuteilung von Ressourcen.
Normalerweise werden Konstruktoren und Destruktoren zugeordnet, aber wenn Sie den Konstruktor zweimal aufrufen (wie beim Aufruf des Konstruktors einer Instanz), wird die Ressource zweimal zugewiesen, aber nur einmal freigegeben.

Wenn Sie den Hauptteil des Konstruktors als normale Methode aufrufen möchten, erstellen Sie eine neue Methode und rufen Sie diese Methode im Konstruktor auf.

z.:

constructor TTest.Create; 
begin 
    inherited; 
    //Allocate needed resources here. 
    Init; 
end; 

procedure TTest.Init; 
begin 
    //Do initialization stuff 
    //But don't allocate any resources 

Jetzt können Sie die init Methode sicher nennen, wann immer ohne jede Chance von Pannen benötigt.

Der Grund, dass es möglich ist, einen Konstruktor aufrufen, ohne „konstruieren“ alles ist, dass der folgende Code arbeiten müssen:

constructor TTest.Create(const AObject: TObject); 
begin    //constructor of TTest happens here 
    inherited Create; //instance call to TParent.Create; 
    //Other code 
end; 

In sehr seltenen Fällen die Klasse Aufruf an den Konstruktor ganz überspringen.
Das wird folgender Code funktioniert:

MyTest:= TTest.NewInstance; 
//Voodoo code here. 
MyTest.Create; 

Dieser den Compiler zu verhindern, verwendet werden kann, um den automatischen Code aus Einfügen, die in einem Aufruf von TTest.Create erzeugt werden.
Dies wird sehr selten benötigt und in 20 Jahren Codierung habe ich es nur einmal benutzt.
Der Anwendungsfall dafür war geschwindigkeitsempfindlichen Code, wo ich den Overhead der Ausnahmebedingung und Nullsetzung von zugewiesenem Speicherplatz vermeiden wollte, dies war vor Delphi unterstützt Datensätze mit Methoden.

Wenn ich diesen Code erneut ausführen müsste, würde ich einen Datensatz verwenden und einfach einen Pool von 1000 Datensätzen auf dem Heap zuweisen und sie nach Bedarf verteilen.

+1

"Sie können auch fortgeschrittenere Sachen machen, wie" TParent.Create "überspringen und" TGrandParent.Create "aufrufen - was dazu führen kann, dass' TParent's Felder nicht richtig initialisiert werden. Tun Sie das nicht, wenn Sie nicht wissen, was Sie tun. Ich denke, eine bessere Erklärung sind die wenigen seltenen Fälle, in denen Sie 'NewInstance' explizit aufrufen müssen, gefolgt von anderem Code, gefolgt von' Create' für diese Instanz. – hvd

+0

@hvd, danke viel besser. Dieser genaue Anwendungsfall ist natürlich der Grund, warum regelmäßige Aufrufe an einen Konstruktor überhaupt möglich sind. – Johan

+0

FWIW, mein Anwendungsfall war ein Konstruktor einer anderen Klasse, die Ereignisse auslöst, sollte es nicht (in anderen Klassen). Ich habe die Klasse mit 'NewInstance' erstellt, einen Verweis darauf gespeichert und die Ereignisse ignoriert, wenn sie aus der Klasse stammen, die gerade erstellt wird. Es macht auch Sinn, den zugewiesenen Platz zu löschen, aber mir war nicht bewusst, dass es einen Unterschied in der Ausnahmebehandlung gab (es ist auch eine lange Zeit für mich), ich habe mich wahrscheinlich falsch erinnert, aber ich dachte, MyTest.Create wäre noch frei die Instanz, wenn während des Baus eine Ausnahme aufgetreten ist. Wenn nicht, danke dafür. – hvd

Verwandte Themen