Esistono diversi modi, ma prima devi capire perché la pulizia degli oggetti è importante, e quindi la ragione std::exitè emarginata tra i programmatori C ++.
RAII e Stack Unwinding
C ++ utilizza un linguaggio chiamato RAII , che in termini semplici significa che gli oggetti devono eseguire l'inizializzazione nel costruttore e la pulizia nel distruttore. Ad esempio, la std::ofstreamclasse [può] aprire il file durante il costruttore, quindi l'utente esegue operazioni di output su di esso e infine alla fine del suo ciclo di vita, solitamente determinato dal suo ambito, viene chiamato il distruttore che essenzialmente chiude il file e scarica qualsiasi contenuto scritto sul disco.
Cosa succede se non si arriva al distruttore per svuotare e chiudere il file? Chissà! Ma forse non scriverà tutti i dati che avrebbe dovuto scrivere nel file.
Ad esempio, considera questo codice
#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);
}
Quello che succede in ogni possibilità è:
- Possibilità 1: Return essenzialmente lascia l'ambito della funzione corrente, quindi è a conoscenza della fine del ciclo di vita di
oschiamare quindi il suo distruttore e fare una pulizia adeguata chiudendo e scaricando il file sul disco.
- Possibilità 2: il lancio di un'eccezione si occupa anche del ciclo di vita degli oggetti nell'ambito corrente, facendo così una pulizia adeguata ...
- Possibilità 3: qui entra in azione lo svolgersi della pila! Anche se viene generata l'eccezione
inner_mad, lo svolgitore eseguirà la pila di made mainper eseguire una corretta pulizia, tutti gli oggetti verranno distrutti correttamente, incluso ptre os.
- Possibilità 4: bene, qui?
exitè una funzione C e non è consapevole né compatibile con gli idiomi C ++. Essa non si esegue la pulizia sui vostri oggetti, tra cui osnello stesso ambito. Quindi il tuo file non verrà chiuso correttamente e per questo motivo il contenuto potrebbe non essere mai scritto al suo interno!
- Altre possibilità: lascerà solo l'ambito principale, eseguendo un implicito
return 0e quindi avendo lo stesso effetto della possibilità 1, ovvero una corretta pulizia.
Ma non essere così sicuro di quello che ti ho appena detto (principalmente possibilità 2 e 3); continua a leggere e scopriremo come eseguire una corretta pulizia basata sulle eccezioni.
Possibili modi per finire
Ritorna dalla principale!
Dovresti farlo quando possibile; preferisco sempre tornare dal tuo programma restituendo uno stato di uscita corretto da quello principale.
Il chiamante del tuo programma, e possibilmente il sistema operativo, potrebbe voler sapere se ciò che il tuo programma avrebbe dovuto fare è stato fatto con successo o no. Per questo stesso motivo è necessario restituire zero o EXIT_SUCCESSsegnalare che il programma è stato chiuso correttamente e EXIT_FAILUREper segnalare che il programma è stato terminato senza successo, qualsiasi altra forma di valore di ritorno è definita dall'implementazione ( §18.5 / 8 ).
Tuttavia potresti essere molto in profondità nello stack di chiamate e restituirlo tutto potrebbe essere doloroso ...
[Non] generare un'eccezione
Il lancio di un'eccezione eseguirà la corretta pulizia degli oggetti utilizzando lo svolgimento dello stack, chiamando il distruttore di ogni oggetto in qualsiasi ambito precedente.
Ma ecco il trucco ! Viene definito dall'implementazione se lo svolgimento dello stack viene eseguito quando un'eccezione generata non viene gestita (dalla clausola catch (...)) o anche se si ha una noexceptfunzione nel mezzo dello stack di chiamate. Questo è indicato nel § 15.5.1 [tranne il termine] :
In alcune situazioni, la gestione delle eccezioni deve essere abbandonata per tecniche di gestione degli errori meno impercettibili. [Nota: queste situazioni sono:
[...]
- quando il meccanismo di gestione delle eccezioni non riesce a trovare un gestore per un'eccezione generata (15.3) o quando la ricerca di un gestore (15.3) incontra il blocco più esterno di una funzione con una noexceptspecifica che non consente l'eccezione (15.4), oppure [...]
[...]
In questi casi, viene chiamato std :: terminate () (18.8.3). Nella situazione in cui non viene trovato alcun gestore di corrispondenza, viene definito dall'implementazione se lo stack viene srotolato o meno prima che std :: terminate () venga chiamato [...]
Quindi dobbiamo prenderlo!
Lancia un'eccezione e prendila in linea di massima!
Poiché le eccezioni non rilevate potrebbero non svolgere lo svolgimento dello stack (e di conseguenza non eseguiranno una corretta pulizia) , dovremmo rilevare l'eccezione in main e quindi restituire uno stato di uscita ( EXIT_SUCCESSo EXIT_FAILURE).
Quindi un setup probabilmente buono sarebbe:
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;
}
/* ... */
}
[Non] std :: exit
Ciò non esegue alcun tipo di svolgimento dello stack e nessun oggetto vivo nello stack chiamerà il rispettivo distruttore per eseguire la pulizia.
Questo è applicato in §3.6.1 / 4 [basic.start.init] :
La chiusura del programma senza uscire dal blocco corrente (ad es. Chiamando la funzione std :: exit (int) (18.5)) non distrugge alcun oggetto con durata di memorizzazione automatica (12.4) . Se viene chiamato std :: exit per terminare un programma durante la distruzione di un oggetto con durata della memoria statica o thread, il programma ha un comportamento indefinito.
Pensaci ora, perché dovresti fare una cosa del genere? Quanti oggetti hai dolorosamente danneggiato?
Altre alternative [come cattive]
Esistono altri modi per terminare un programma (oltre all'arresto anomalo) , ma non sono consigliati. Per motivi di chiarezza verranno presentati qui. Notare come la normale chiusura del programma non significhi lo svolgersi dello stack ma uno stato corretto per il sistema operativo.
std::_Exit provoca una normale chiusura del programma, e il gioco è fatto.
std::quick_exitprovoca una normale chiusura del programma e chiama i std::at_quick_exitgestori, non viene eseguita alcuna altra pulizia.
std::exitprovoca una normale chiusura del programma e quindi chiama i std::atexitgestori. Vengono eseguiti altri tipi di pulizia come la chiamata di distruttori di oggetti statici.
std::abortprovoca una chiusura anomala del programma, non viene eseguita alcuna pulizia. Questo dovrebbe essere chiamato se il programma è terminato in un modo davvero inaspettato. Non farà altro che segnalare al sistema operativo la chiusura anomala. Alcuni sistemi eseguono un dump core in questo caso.
std::terminatechiama il std::terminate_handlerquale chiama std::abortper impostazione predefinita.
main()ritorno di utilizzo, nelle funzioni utilizzare un valore di ritorno corretto o generare un'eccezione corretta. Non usareexit()!