@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
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
Der Body dieses Konstruktors ist leer. Solange die Konstrukteure für base1
, member2
und member3
Ausnahme sicher sind, gibt es nichts zu befürchten. Wenn zum Beispiel der Konstruktor von member2
wirft, ist dieser Konstruktor dafür verantwortlich, sich selbst zu bereinigen. Die Basis base1
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ür unique_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.