Ich habe eine CPP-Klasse, deren Konstruktor einige Operationen ausführt. Einige dieser Vorgänge schlagen möglicherweise fehl. Ich weiß, dass Konstrukteure nichts zurückgeben.
Meine Fragen sind:
Dürfen andere Operationen als das Initialisieren von Mitgliedern in einem Konstruktor ausgeführt werden?
Ist es möglich, der aufrufenden Funktion mitzuteilen, dass einige Operationen im Konstruktor fehlgeschlagen sind?
Kann ich
new ClassName()
NULL zurückgeben, wenn im Konstruktor Fehler auftreten?
object-oriented
c++
MayurK
quelle
quelle
Square
mit einem Konstruktor haben, der einen Parameter, die Länge einer Seite, akzeptiert, möchten Sie überprüfen, ob dieser Wert größer als 0 ist.Antworten:
Ja, obwohl einige Codierungsstandards dies möglicherweise untersagen.
Ja. Der empfohlene Weg ist, eine Ausnahme auszulösen. Alternativ können Sie die Fehlerinformationen im Objekt speichern und Methoden für den Zugriff auf diese Informationen bereitstellen.
Nein.
quelle
Sie können eine statische Methode erstellen, die die Berechnung ausführt und bei Erfolg oder Nichterfolg ein Objekt zurückgibt.
Abhängig davon, wie diese Konstruktion des Objekts ausgeführt wird, ist es möglicherweise besser, ein anderes Objekt zu erstellen, das die Konstruktion von Objekten in einer nicht statischen Methode ermöglicht.
Indirektes Aufrufen eines Konstruktors wird häufig als "Factory" bezeichnet.
Auf diese Weise können Sie auch ein Nullobjekt zurückgeben. Dies ist möglicherweise eine bessere Lösung als die Rückgabe von Null.
quelle
NULL
. Inint foo() { return NULL
würden Sie beispielsweise tatsächlich0
(Null) ein Ganzzahlobjekt zurückgeben. In würdenstd::string foo() { return NULL; }
Sie versehentlich aufrufen,std::string::string((const char*)NULL)
was Undefiniertes Verhalten ist (NULL zeigt nicht auf eine mit \ 0 abgeschlossene Zeichenfolge).int
. ZBstd::allocator<int>
ist eine vollkommen gesunde Fabrik.@SebastianRedl gab bereits die einfachen, direkten Antworten, aber einige zusätzliche Erklärungen könnten nützlich sein.
TL; DR = Es gibt eine Stilregel, um Konstruktoren einfach zu halten, es gibt Gründe dafür, aber diese Gründe beziehen sich meist auf einen historischen (oder einfach schlechten) Codierungsstil. Die Behandlung von Ausnahmen in Konstruktoren ist gut definiert, und Destruktoren werden weiterhin für vollständig konstruierte lokale Variablen und Member aufgerufen, was bedeutet, dass es im idiomatischen C ++ - Code keine Probleme geben sollte. Die Stilregel bleibt trotzdem bestehen, aber normalerweise ist dies kein Problem - nicht alle Initialisierungen müssen im Konstruktor und insbesondere nicht unbedingt im Konstruktor erfolgen.
Es ist eine gängige Stilregel, dass Konstruktoren das absolute Minimum tun sollten, um einen definierten gültigen Status einzurichten. Wenn Ihre Initialisierung komplexer ist, sollte sie außerhalb des Konstruktors behandelt werden. Wenn Ihr Konstruktor keinen kostengünstigen Initialisierungswert einrichten kann, sollten Sie die von Ihrer Klasse erzwungenen Invaranten abschwächen, um einen hinzuzufügen. Wenn das Zuweisen von Speicherplatz für die Verwaltung Ihrer Klasse beispielsweise zu teuer ist, fügen Sie einen noch nicht zugewiesenen Nullstatus hinzu, da natürlich Sonderfälle wie Null nie zu Problemen geführt haben. Hm.
Obwohl üblich, sicherlich in dieser extremen Form ist es sehr weit vom Absoluten entfernt. Insbesondere bin ich, wie mein Sarkasmus zeigt, im Lager, das besagt, dass es fast immer zu teuer ist, Invarianten zu schwächen. Es gibt jedoch Gründe für die Stilregel, und es gibt Möglichkeiten, sowohl minimale Konstruktoren als auch starke Invarianten zu haben.
Die Gründe dafür liegen in der automatischen Destruktor-Bereinigung, insbesondere angesichts von Ausnahmen. Grundsätzlich muss es einen genau definierten Punkt geben, an dem der Compiler für den Aufruf von Destruktoren verantwortlich ist. Während Sie sich noch in einem Konstruktoraufruf befinden, ist das Objekt nicht unbedingt vollständig konstruiert, sodass es nicht gültig ist, den Destruktor für dieses Objekt aufzurufen. Daher wird die Verantwortung für die Zerstörung des Objekts erst dann auf den Compiler übertragen, wenn der Konstruktor erfolgreich abgeschlossen wurde. Dies ist als RAII (Resource Allocation Is Initialization) bekannt, was nicht wirklich der beste Name ist.
Wenn ein Ausnahmefehler im Konstruktor auftritt, muss alles, was als Teil erstellt wurde, explizit bereinigt werden, normalerweise in a
try .. catch
.Allerdings Komponenten des Objekts , das bereits erfolgreich aufgebaut sind bereits die Compiler Verantwortung. Dies bedeutet, dass es in der Praxis keine große Sache ist. z.B
Der Body dieses Konstruktors ist leer. Solange die Konstrukteure für
base1
,member2
undmember3
Ausnahme sicher sind, gibt es nichts zu befürchten. Wenn zum Beispiel der Konstruktor vonmember2
wirft, ist dieser Konstruktor dafür verantwortlich, sich selbst zu bereinigen. Die Basisbase1
wurde bereits vollständig erstellt, sodass der Destruktor automatisch aufgerufen wird.member3
wurde noch nie teilweise gebaut, muss also nicht aufgeräumt werden.Auch wenn es einen Body gibt, werden lokale Variablen, die vor dem Auslösen der Exception vollständig erstellt wurden, wie jede andere Funktion automatisch zerstört. Konstruktorkörper, die mit unformatierten Zeigern jonglieren oder einen impliziten Zustand "besitzen" (der an einer anderen Stelle gespeichert ist) - was normalerweise bedeutet, dass ein Funktionsaufruf begin / acquis mit einem Aufruf end / release abgeglichen werden muss - können Ausnahmesicherheitsprobleme verursachen, aber das eigentliche Problem dort kann eine Ressource nicht ordnungsgemäß über eine Klasse verwalten. Wenn Sie beispielsweise
unique_ptr
im Konstruktor rohe Zeiger durch ersetzen , wird der Destruktor fürunique_ptr
bei Bedarf automatisch aufgerufen.Es gibt noch andere Gründe, warum die Leute Do-the-Minimum-Konstruktoren bevorzugen. Zum einen gehen viele Leute davon aus, dass Konstruktoraufrufe billig sind, weil die Stilregel existiert. Eine Möglichkeit, dies zu erreichen und dennoch starke Invarianten zu haben, besteht darin, eine separate Factory- / Builder-Klasse zu haben, die stattdessen die abgeschwächten Invarianten enthält und die den erforderlichen Anfangswert mithilfe (möglicherweise vieler) normaler Memberfunktionsaufrufe einrichtet. Wenn Sie den erforderlichen Anfangszustand erreicht haben, übergeben Sie dieses Objekt als Argument an den Konstruktor für die Klasse mit den starken Invarianten. Das kann den Mut des Objekts mit schwachen Invarianten "stehlen" - die Semantik verschieben - was eine billige (und normalerweise
noexcept
) Operation ist.Und natürlich können Sie das in eine
make_whatever ()
Funktion einschließen, sodass Aufrufer dieser Funktion niemals die Klasseninstanz "Geschwächte Invarianten" sehen müssen.quelle
main
Funktion oder zu statischen / globalen Variablen. Ein Objekt zugeordnet Verwendungnew
, ist nicht im Besitz , bis Sie diese Verantwortung zuweisen, aber intelligente Zeiger besitzen die Halde zugewiesenen Objekte , die sie verweisen, und Behälter besitzen ihre Datenstrukturen. Eigentümer können wählen, vorzeitig zu löschen, der Eigentümer Destruktor ist letztendlich verantwortlich.