Es gibt verschiedene Möglichkeiten, aber zuerst müssen Sie verstehen, warum die Objektbereinigung wichtig ist, und daher ist der Grund std::exit
unter C ++ - Programmierern marginalisiert.
RAII und Stack Unwinding
C ++ verwendet eine Redewendung namens RAII , was in einfachen Worten bedeutet, dass Objekte eine Initialisierung im Konstruktor und eine Bereinigung im Destruktor durchführen sollten. Zum Beispiel diestd::ofstream
Klasse die Datei während des Konstruktors öffnen, dann führt der Benutzer Ausgabeoperationen für sie aus, und schließlich wird am Ende ihres Lebenszyklus, der normalerweise durch seinen Umfang bestimmt wird, der Destruktor aufgerufen, der die Datei im Wesentlichen schließt und leert Alle auf die Festplatte geschriebenen Inhalte.
Was passiert, wenn Sie nicht zum Destruktor gelangen, um die Datei zu leeren und zu schließen? Wer weiß! Möglicherweise werden jedoch nicht alle Daten in die Datei geschrieben.
Betrachten Sie zum Beispiel diesen Code
#include <fstream>
#include <exception>
#include <memory>
void inner_mad()
{
throw std::exception();
}
void mad()
{
auto ptr = std::make_unique<int>();
inner_mad();
}
int main()
{
std::ofstream os("file.txt");
os << "Content!!!";
int possibility = /* either 1, 2, 3 or 4 */;
if(possibility == 1)
return 0;
else if(possibility == 2)
throw std::exception();
else if(possibility == 3)
mad();
else if(possibility == 4)
exit(0);
}
Was bei jeder Möglichkeit passiert, ist:
- Möglichkeit 1: Return verlässt im Wesentlichen den aktuellen Funktionsumfang, sodass es über das Ende des Lebenszyklus
os
informiert ist, indem es seinen Destruktor aufruft und eine ordnungsgemäße Bereinigung durch Schließen und Leeren der Datei auf die Festplatte durchführt.
- Möglichkeit 2: Durch das Auslösen einer Ausnahme wird auch der Lebenszyklus der Objekte im aktuellen Bereich berücksichtigt, wodurch eine ordnungsgemäße Bereinigung durchgeführt wird ...
- Möglichkeit 3: Hier tritt das Abwickeln des Stapels in Aktion! Obwohl die Ausnahme ausgelöst wird
inner_mad
, durchläuft der Abwickler den Stapel von mad
und führt main
eine ordnungsgemäße Bereinigung durch. Alle Objekte werden ordnungsgemäß zerstört, einschließlich ptr
und os
.
- Möglichkeit 4: Nun, hier?
exit
ist eine C-Funktion und weder bekannt noch kompatibel mit den C ++ - Redewendungen. Es werden keine Bereinigungen für Ihre Objekte durchgeführt, auch nicht os
im selben Bereich. Ihre Datei wird also nicht richtig geschlossen und aus diesem Grund wird der Inhalt möglicherweise nie in sie geschrieben!
- Andere Möglichkeiten: Es verlässt nur den Hauptbereich, indem es eine implizite Ausführung durchführt
return 0
und somit den gleichen Effekt wie Möglichkeit 1 hat, dh eine ordnungsgemäße Bereinigung.
Aber sei dir nicht so sicher, was ich dir gerade gesagt habe (hauptsächlich Möglichkeiten 2 und 3); Lesen Sie weiter und wir werden herausfinden, wie Sie eine ordnungsgemäße ausnahmebasierte Bereinigung durchführen.
Mögliche Möglichkeiten zum Beenden
Rückkehr von der Hauptstraße!
Sie sollten dies nach Möglichkeit tun. Ziehen Sie es immer vor, von Ihrem Programm zurückzukehren, indem Sie einen ordnungsgemäßen Exit-Status von main zurückgeben.
Der Aufrufer Ihres Programms und möglicherweise das Betriebssystem möchten möglicherweise wissen, ob das, was Ihr Programm tun sollte, erfolgreich ausgeführt wurde oder nicht. Aus dem gleichen Grund sollten Sie entweder Null zurückgeben oder EXIT_SUCCESS
signalisieren, dass das Programm erfolgreich beendet wurde, und EXIT_FAILURE
um zu signalisieren, dass das Programm nicht erfolgreich beendet wurde , ist jede andere Form von Rückgabewert implementierungsdefiniert ( §18.5 / 8 ).
Sie befinden sich jedoch möglicherweise sehr tief im Aufrufstapel, und es kann schmerzhaft sein, alles zurückzugeben ...
[Nicht] eine Ausnahme auslösen
Durch das Auslösen einer Ausnahme wird eine ordnungsgemäße Objektbereinigung mithilfe des Abwickelns des Stapels durchgeführt, indem der Destruktor jedes Objekts in einem früheren Bereich aufgerufen wird.
Aber hier ist der Haken ! Es ist implementierungsdefiniert, ob das Abwickeln des Stapels durchgeführt wird, wenn eine ausgelöste Ausnahme nicht behandelt wird (durch die catch (...) -Klausel) oder selbst wenn Sie eine noexcept
Funktion in der Mitte des Aufrufstapels haben. Dies ist in §15.5.1 [außer.terminate] angegeben :
In einigen Situationen muss die Ausnahmebehandlung für weniger subtile Fehlerbehandlungstechniken abgebrochen werden. [Hinweis: Diese Situationen sind:
[...]
- wenn der Ausnahmebehandlungsmechanismus keinen Handler für eine ausgelöste Ausnahme finden kann (15.3) oder wenn die Suche nach einem Handler (15.3) auf den äußersten Block einer Funktion mit einer noexcept
Spezifikation stößt, die die Ausnahme nicht zulässt (15.4), oder [...]
[...]
In solchen Fällen heißt std :: terminate () (18.8.3). In der Situation, in der kein passender Handler gefunden wird, wird implementierungsdefiniert, ob der Stapel abgewickelt wird, bevor std :: terminate () [...] aufgerufen wird.
Also müssen wir es fangen!
Wirf eine Ausnahme und fange sie an der Hauptleitung!
Da nicht erfasste Ausnahmen möglicherweise kein Abwickeln des Stapels durchführen (und folglich keine ordnungsgemäße Bereinigung durchführen) , sollten wir die Ausnahme in main abfangen und dann einen Exit-Status zurückgeben ( EXIT_SUCCESS
oderEXIT_FAILURE
) zurückgeben.
Ein möglicherweise gutes Setup wäre also:
int main()
{
/* ... */
try
{
// Insert code that will return by throwing a exception.
}
catch(const std::exception&) // Consider using a custom exception type for intentional
{ // throws. A good idea might be a `return_exception`.
return EXIT_FAILURE;
}
/* ... */
}
[Nicht] std :: exit
Dies führt keine Abwicklung des Stapels durch, und kein lebendes Objekt auf dem Stapel ruft seinen jeweiligen Destruktor auf, um eine Bereinigung durchzuführen.
Dies wird in §3.6.1 / 4 [basic.start.init] durchgesetzt :
Das Beenden des Programms ohne Verlassen des aktuellen Blocks (z. B. durch Aufrufen der Funktion std :: exit (int) (18.5)) zerstört keine Objekte mit automatischer Speicherdauer (12.4) . Wenn std :: exit aufgerufen wird, um ein Programm während der Zerstörung eines Objekts mit statischer oder Thread-Speicherdauer zu beenden, hat das Programm ein undefiniertes Verhalten.
Denken Sie jetzt darüber nach, warum sollten Sie so etwas tun? Wie viele Gegenstände haben Sie schmerzhaft beschädigt?
Andere [als schlechte] Alternativen
Es gibt andere Möglichkeiten, ein Programm zu beenden (außer Abstürzen) , aber sie werden nicht empfohlen. Nur zur Verdeutlichung werden sie hier vorgestellt. Beachten Sie, wie normale Programmbeendigung nicht mittleres Stack Abwickeln , sondern eine Ordnung Zustand für das Betriebssystem.
std::_Exit
verursacht eine normale Programmbeendigung, und das war's.
std::quick_exit
bewirkt eine normale Programmbeendigung und ruft std::at_quick_exit
Handler auf, es wird keine weitere Bereinigung durchgeführt.
std::exit
verursacht eine normale Programmbeendigung und ruft dann std::atexit
Handler auf. Andere Arten von Bereinigungen werden durchgeführt, z. B. das Aufrufen von Destruktoren für statische Objekte.
std::abort
verursacht eine abnormale Programmbeendigung, es wird keine Bereinigung durchgeführt. Dies sollte aufgerufen werden, wenn das Programm auf eine wirklich, wirklich unerwartete Weise beendet wurde. Es wird nichts anderes tun, als dem Betriebssystem die abnormale Beendigung zu signalisieren. Einige Systeme führen in diesem Fall einen Core-Dump durch.
std::terminate
ruft das auf, std::terminate_handler
was std::abort
standardmäßig aufruft .
main()
Verwendung return, bei Funktionen wird ein korrekter Rückgabewert verwendet oder eine ordnungsgemäße Ausnahme ausgelöst. Nicht benutzenexit()
!