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::ofstream
classe [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
os
chiamare 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 mad
e main
per eseguire una corretta pulizia, tutti gli oggetti verranno distrutti correttamente, incluso ptr
e 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 os
nello 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 0
e 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_SUCCESS
segnalare che il programma è stato chiuso correttamente e EXIT_FAILURE
per 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 noexcept
funzione 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 noexcept
specifica 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_SUCCESS
o 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_exit
provoca una normale chiusura del programma e chiama i std::at_quick_exit
gestori, non viene eseguita alcuna altra pulizia.
std::exit
provoca una normale chiusura del programma e quindi chiama i std::atexit
gestori. Vengono eseguiti altri tipi di pulizia come la chiamata di distruttori di oggetti statici.
std::abort
provoca 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::terminate
chiama il std::terminate_handler
quale chiama std::abort
per impostazione predefinita.
main()
ritorno di utilizzo, nelle funzioni utilizzare un valore di ritorno corretto o generare un'eccezione corretta. Non usareexit()
!