Ich habe als Teil der Aufgabe, in ein Entwicklungskit zu schauen, das die "zweiphasige" Konstruktion für C ++ - Klassen verwendet:
%Vor%In der Quelle
%Vor%Warum sollte dieses "Zwei-Phasen-System" verwendet werden und welche Vorteile gibt es? Warum nicht einfach die Objektinitialisierung innerhalb des tatsächlichen Konstruktors instantiieren?
Ein Dokument über Zwei-Phasen-Konstruktion .
Die Idee ist, dass Sie keinen Wert von einem Konstruktor zurückgeben können, um einen Fehler anzuzeigen. Die einzige Möglichkeit, einen Konstruktorfehler anzuzeigen, ist das Auslösen einer Ausnahme. Dies ist nicht immer wünschenswert, nicht zuletzt deshalb, weil die Ausnahmesicherheit ein sehr komplexes Thema ist.
In diesem Fall wird also die Konstruktion aufgeteilt: ein Konstruktor, der nicht auslöst, aber auch nicht vollständig initialisiert, und eine Funktion, die die Initialisierung durchführt und eine Erfolgs- oder Fehleranzeige zurückgeben kann, ohne (notwendigerweise) Ausnahmen auszulösen.
Es gibt keinen guten Grund, dies zu tun - zu vermeiden. Der ursprüngliche Autor des Codes wusste wahrscheinlich einfach nicht, was er tat.
Dies ist nützlich, wenn Sie dem Benutzer (Ihrer Klasse) mehr Kontrolle über die Ressourcenzuweisung / -zuordnung geben müssen. Denken Sie zum Beispiel an eine Socket-Klasse. Der Benutzer gibt die Host- und Port-Parameter an den Konstruktor weiter und möchte möglicherweise das tatsächliche "Öffnen" der Sockets zu einem späteren Zeitpunkt verzögern (d. H. Das SOCKET-Objekt auf niedriger Ebene zuordnen). Er möchte vielleicht auch den Sockel nach Belieben schließen und wieder öffnen. Zweiphasige Konstruktion (oder Lazy Initialization ) erleichtert dies. Eine solche Socket-Schnittstelle sieht wie folgt aus:
%Vor%Übrigens ist dieses Idiom kein Ersatz dafür, dass Ausnahmen vom Konstruktor ausgeschlossen werden. Wenn der Konstruktor fehlschlägt, werfen Sie eine Ausnahme aus. Weitere Informationen finden Sie in dieser BS-FAQ und der Eintrag bei C ++ - FAQ-Lite .
Warum sollte dieses "Zwei-Phasen-System" verwendet werden und welche Vorteile gibt es?
Dieses Idiom wird aus zwei Gründen verwendet:
Dieses Idiom wurde in der Vergangenheit verwendet, bevor die Verwendung von Ausnahmen in der Sprache standardisiert wurde, und von Bibliotheksdesignern, die Ausnahmen nicht verstanden (oder aus irgendeinem Grund nicht verwenden wollten).
Sie finden es immer noch in veraltetem / abwärtskompatiblem Code (z. B. MFC).
Construction
-Memberfunktion als (rein) virtuell und erlauben spezialisierten Klassen, sie zu ersetzen. Das ist fast immer [1] ein Zeichen oder ein schlechtes Design (Sie können und sollten Ihren Code implementieren, damit Sie dieses Idiom nicht brauchen). [1] - "fast immer" bedeutet hier, dass ich überhaupt keinen Grund sehe, es zu tun, aber ich vermisse etwas:).
Meine Antwort aus den Schützengräben ist, dass ein zweiphasiger Aufbau - insbesondere bei der Hardware-Initialisierung - ein Muss ist. Wenn in der realen Welt ein Objekt versagt, etwas zu konstruieren oder zu kontrollieren, dann wartet ein Albtraumszenario nur darauf, zu passieren.
Wenn Hardwarekontrollklassen die Hardware in ihren Konstruktoren initialisieren (die an Dingen angebracht ist, die Menschen verbiegen, falten, spindeln oder verstümmeln können), muss sich die gesamte Architektur der Anwendung ändern, um dies zu berücksichtigen. Es ändert sich, weil ich meine Objekte nicht so zusammenstellen kann, wie ich es möchte. Vielleicht möchte ich einen Master-Controller, der alle Sub-Controller einbettet. Ich will die Freiheit, sie jederzeit zu konstruieren. Objekte, die nicht konstruieren können, oder Hardware ankurbeln, wenn sie konstruiert werden, werfen einen riesigen Schraubenschlüssel in die Arbeiten. In diesen Fällen muss ich die Erstellung von Objekten auf die richtige Zeit verschieben. Das bedeutet Zeiger auf Objekte - die vielleicht noch null sind und Sie dazu zwingen, mit Null-Zeigern auf große wichtige Objekte zu reagieren. Ich hasse es, dazu gezwungen zu werden.
In meiner Weltansicht möchte ich, dass meine Konstruktoren harmlos sind, immer fehlerfrei arbeiten, einfach alle ihre Eigenschaften initialisieren (und vielleicht einige Argumente auf sie anwenden) - und sonst nichts. Bereite das Objekt einfach so vor, dass ich es konstruieren kann, oder beziehe es immer und überall auf mich, wenn ich es möchte. Ich werde die 'Verbindung' oder 'Initialisierung' der großen, wichtigen Stücke zu einer Zeit und an einem Ort meiner Wahl durchführen, vorzugsweise mit einer offenen (...) oder init (...) Methode.
Die angegebene Quelle ist extrem bizarr.
Wenn es C ++ ist, kann new
nicht 0 zurückgeben und sollte eine Ausnahme auslösen.
Wenn es etwas ist, das C ++ nicht ganz unähnlich ist, wobei new bei einem Fehler 0 zurückgibt (es ist bekannt, dass solche Dinge existieren), dann ist die Konstruktion äquivalent zu:
%Vor%Jetzt haben wir ein Problem - wenn Construction zweimal aufgerufen wird, konstruieren wir es erneut und geben wie oben true zurück, konstruieren es nicht erneut und geben wahr zurück, wie es konstruiert ist, oder konstruieren es nicht erneut und geben false als zurück es ist ein Fehler, etwas zweimal zu konstruieren?
Manchmal - beispielsweise für Klassen, die externe Ressourcen verwalten - möchten Sie sie möglicherweise als eine von Ihnen erstellte Zustandsmaschine implementieren und dann die Ressource zuweisen. Dies ist sinnvoll, wenn eine Ressource ungültig werden kann, sodass Sie die Operationen, die Sie ausführen, trotzdem überprüfen müssen. Eine Umwandlung vom Typ in bool ist das Muster, das in der Standardbibliothek für solche Ressourcen verwendet wird. Normalerweise sollten Sie ein Objekt auf einmal erstellen und erwarten, dass es gültig ist.
Ein Grund dafür könnte sein, dass Konstruktoren keine Werte zurückgeben. Einige Personen bevorzugen eine Create ähnliche Funktion, die nach der Instanziierung des Objekts aufgerufen werden sollte. Falls Sie keine Ausnahme verwenden, da sie viel Code erzeugen (besonders in der eingebetteten Welt), ist es unmöglich, nur den Konstruktor zu verwenden, da dieser keine Garantien bietet (wie ich bereits sagte, kann er keine Werte zurückgeben).
Andere Technik, die ich sah, war etwa so:
%Vor%Es macht im Grunde die Konstruktion innerhalb des Konstruktors und hält das Ergebnis der Operation in einer Variablen - nicht platzeffizient.
Andere haben hier den Fall erwähnt, in dem die Methode Construction () virtuell ist. Eine Situation, in der dies eine nützliche Rolle spielen kann, ist die Deserialisierung eines Objektgraphen (die RogueWave Tools.h ++ verwendet diese Technik IIRC):
Noch eine andere Verwendung für diese Technik wird hier beschrieben: als Mittel, um gemeinsamen Code von außen zu faktorisieren einer Anzahl von überladenen Konstruktoren.
Ich wäre sicherlich kein Fan dieser zweistufigen Konstruktion. Es gibt viele andere Möglichkeiten, um eine Ausnahme / einen Fehler in einem Konstruktor auszulösen. Erstens sollte jeder C ++ - Compiler, der sein Gewicht wert ist, in der Lage sein, eine Exception von einem Konstruktor zu werfen und zu erlauben, dass er entsprechend abgefangen wird.
Eine andere Lösung wäre, einen internen Fehlercode ähnlich dem Posix errno -Ansatz zu haben. Und der Aufrufer könnte dann diesen Fehlercode durch einen Aufruf an ein Mitglied der Klasse abfragen. IIR Windows hat so etwas mit der Funktion GetLastError .