Ich möchte eine Datei in einem Klassenkonstruktor öffnen. Es ist möglich, dass die Öffnung fehlschlägt und die Objektkonstruktion nicht abgeschlossen werden kann. Wie gehe ich mit diesem Fehler um? Ausnahme rauswerfen?
Ja.
Wenn dies möglich ist, wie geht man damit in einem Nicht-Throw-Konstruktor um?
Ihre Optionen sind:
- Neugestaltung der App, sodass keine Konstruktoren benötigt werden, um nicht zu werfen - tun Sie es wirklich, wenn möglich
- Fügen Sie eine Flagge hinzu und testen Sie den erfolgreichen Bau
- Sie könnten jede Mitgliedsfunktion, die legitimerweise unmittelbar nach dem Konstruktortest aufgerufen wird, das Flag testen lassen, idealerweise auslösen, wenn es gesetzt ist, aber ansonsten einen Fehlercode zurückgeben
- Dies ist hässlich und schwer zu korrigieren, wenn eine flüchtige Gruppe von Entwicklern am Code arbeitet.
- Sie können dies zur Kompilierungszeit überprüfen, indem Sie das Objekt polymorph auf eine von zwei Implementierungen verschieben lassen: eine erfolgreich erstellte und eine immer fehlerhafte Version, die jedoch Heap-Nutzung und Leistungskosten verursacht.
- Sie können die Last der Überprüfung des Flags vom aufgerufenen Code auf den Angerufenen verlagern, indem Sie eine Anforderung dokumentieren, dass sie eine "is_valid ()" - oder ähnliche Funktion aufrufen, bevor Sie das Objekt verwenden: wieder fehleranfällig und hässlich, aber noch verteilter, nicht durchsetzbar und außer Kontrolle.
- Sie können dies für den Aufrufer ein wenig einfacher und lokaler gestalten, wenn Sie Folgendes unterstützen:
if (X x) ...(dh das Objekt kann in einem booleschen Kontext ausgewertet werden, normalerweise durch Bereitstellung operator bool() constoder ähnliche integrale Konvertierung), aber dann haben Sie keinen xSpielraum dafür Fragen Sie nach Details des Fehlers. Dies kann zif (std::ifstream f(filename)) { ... } else ...;
- Lassen Sie den Anrufer einen Stream bereitstellen, für dessen Öffnen er verantwortlich ist ... (bekannt als Dependency Injection oder DI) ... in einigen Fällen funktioniert dies nicht so gut:
- Sie können immer noch Fehler haben, wenn Sie den Stream in Ihrem Konstruktor verwenden. Was dann?
- Die Datei selbst kann ein Implementierungsdetail sein, das für Ihre Klasse privat sein sollte, anstatt für den Aufrufer verfügbar zu sein. Was ist, wenn Sie diese Anforderung später entfernen möchten? Beispiel: Sie haben möglicherweise eine Nachschlagetabelle mit vorberechneten Ergebnissen aus einer Datei gelesen, Ihre Berechnungen jedoch so schnell durchgeführt, dass keine Vorberechnung erforderlich ist. Es ist schmerzhaft (in einer Unternehmensumgebung manchmal sogar unpraktisch), die Datei zu jedem Zeitpunkt zu entfernen Client-Nutzung und erzwingt viel mehr Neukompilierung als potenziell einfaches erneutes Verknüpfen.
- Erzwingen Sie, dass der Aufrufer einen Puffer für eine Erfolgs- / Fehler- / Fehlerbedingungsvariable bereitstellt, die der Konstruktor festlegt: z
bool worked; X x(&worked); if (worked) ...
- Diese Belastung und Ausführlichkeit zieht die Aufmerksamkeit auf sich und macht den Aufrufer hoffentlich viel bewusster für die Notwendigkeit, die Variable nach der Konstruktion des Objekts zu konsultieren
- Erzwingen Sie, dass der Aufrufer das Objekt über eine andere Funktion erstellt, die Rückkehrcodes und / oder Ausnahmen verwenden kann:
if (X* p = x_factory()) ...
- Smart_Ptr_Throws_On_Null_Deref p_x = x_factory ();
</li>
<li>X x; // nie verwendbar; if (init_x (& x)) ... `
- etc...
Kurz gesagt, C ++ bietet elegante Lösungen für diese Art von Problemen: in diesem Fall Ausnahmen. Wenn Sie sich künstlich daran hindern, sie zu verwenden, erwarten Sie nicht, dass es etwas anderes gibt, das halb so gute Arbeit leistet.
(PS Ich mag es, Variablen zu übergeben, die durch einen Zeiger geändert werden - wie workedoben beschrieben - ich weiß, dass die FAQ lite davon abhält, aber mit der Begründung nicht einverstanden ist. Ich bin nicht besonders an einer Diskussion darüber interessiert, es sei denn, Sie haben etwas, das nicht in den FAQ behandelt wird.)