Il existe plusieurs façons, mais vous devez d'abord comprendre pourquoi le nettoyage des objets est important, et donc la raison std::exit
est marginalisée parmi les programmeurs C ++.
RAII et déroulement de la pile
C ++ utilise un idiome appelé RAII , ce qui signifie en termes simples que les objets doivent effectuer l'initialisation dans le constructeur et le nettoyage dans le destructeur. Par exemple, la std::ofstream
classe [peut] ouvrir le fichier pendant le constructeur, puis l'utilisateur effectue des opérations de sortie sur celui-ci, et enfin à la fin de son cycle de vie, généralement déterminé par sa portée, le destructeur est appelé qui ferme essentiellement le fichier et vide tout contenu écrit sur le disque.
Que se passe-t-il si vous n'obtenez pas le destructeur pour vider et fermer le fichier? Qui sait! Mais il est possible qu'il n'écrive pas toutes les données qu'il était censé écrire dans le fichier.
Par exemple, considérez ce 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);
}
Ce qui se passe dans chaque possibilité est:
- Possibilité 1: Return laisse essentiellement la portée de la fonction actuelle, donc il connaît la fin du cycle de vie d'
os
appeler ainsi son destructeur et de faire un nettoyage approprié en fermant et en vidant le fichier sur le disque.
- Possibilité 2: le fait de lever une exception prend également en charge le cycle de vie des objets dans la portée actuelle, effectuant ainsi un nettoyage approprié ...
- Possibilité 3: Ici, le déroulement de la pile entre en action! Même si l'exception est lancée
inner_mad
, le dérouleur passera par la pile de mad
et main
pour effectuer un nettoyage approprié, tous les objets seront détruits correctement, y compris ptr
et os
.
- Possibilité 4: Eh bien, ici?
exit
est une fonction C et elle n'est pas consciente ni compatible avec les idiomes C ++. Il n'effectue pas de nettoyage sur vos objets, y compris os
dans la même portée. Votre fichier ne sera donc pas fermé correctement et pour cette raison, le contenu pourrait ne jamais y être écrit!
- Autres possibilités: il ne restera plus que la portée principale, en effectuant un implicite
return 0
et en ayant ainsi le même effet que la possibilité 1, c'est-à-dire un nettoyage correct.
Mais ne soyez pas si sûr de ce que je viens de vous dire (principalement les possibilités 2 et 3); continuez à lire et nous découvrirons comment effectuer un nettoyage approprié basé sur les exceptions.
Façons possibles de mettre fin
Retour de main!
Vous devriez le faire chaque fois que possible; préférez toujours revenir de votre programme en renvoyant un état de sortie correct de main.
L'appelant de votre programme, et peut-être le système d'exploitation, voudra peut-être savoir si ce que votre programme était censé faire a été fait avec succès ou non. Pour cette même raison, vous devez retourner zéro ou EXIT_SUCCESS
signaler que le programme s'est terminé avec succès et EXIT_FAILURE
signaler que le programme s'est terminé sans succès, toute autre forme de valeur de retour est définie par l'implémentation ( §18.5 / 8 ).
Cependant, vous pouvez être très profondément dans la pile des appels, et retourner tout cela peut être douloureux ...
[Ne pas] lever une exception
Le lancement d'une exception effectuera un nettoyage d'objet approprié à l'aide du déroulement de la pile, en appelant le destructeur de chaque objet dans n'importe quelle portée précédente.
Mais voici le hic ! Il est défini par l'implémentation si le déroulement de la pile est effectué lorsqu'une exception levée n'est pas gérée (par la clause catch (...)) ou même si vous avez une noexcept
fonction au milieu de la pile des appels. Ceci est indiqué au §15.5.1 [sauf.terminate] :
Dans certaines situations, la gestion des exceptions doit être abandonnée pour des techniques de gestion des erreurs moins subtiles. [Remarque: ces situations sont les suivantes:
[...]
- lorsque le mécanisme de gestion des exceptions ne peut pas trouver de gestionnaire pour une exception levée (15.3), ou lorsque la recherche d'un gestionnaire (15.3) rencontre le bloc le plus à l'extérieur d'une fonction avec une noexcept
spécification qui n'autorise pas l'exception (15.4), ou [...]
[...]
Dans de tels cas, std :: terminate () est appelé (18.8.3). Dans le cas où aucun gestionnaire correspondant n'est trouvé, il est défini par l'implémentation si la pile est déroulée ou non avant que std :: terminate () soit appelé [...]
Nous devons donc l'attraper!
Jetez une exception et attrapez-la au principal!
Étant donné que les exceptions non capturées peuvent ne pas effectuer le déroulement de la pile (et, par conséquent, ne pas effectuer le nettoyage approprié) , nous devons intercepter l'exception en principal, puis renvoyer un état de sortie ( EXIT_SUCCESS
ou EXIT_FAILURE
).
Donc, une bonne configuration pourrait être:
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;
}
/* ... */
}
[Ne pas] std :: exit
Cela n'effectue aucune sorte de déroulement de la pile, et aucun objet vivant sur la pile n'appellera son destructeur respectif pour effectuer le nettoyage.
Ceci est appliqué au §3.6.1 / 4 [basic.start.init] :
Terminer le programme sans quitter le bloc courant (par exemple, en appelant la fonction std :: exit (int) (18.5)) ne détruit aucun objet avec une durée de stockage automatique (12.4) . Si std :: exit est appelé pour terminer un programme pendant la destruction d'un objet avec une durée de stockage statique ou de thread, le programme a un comportement indéfini.
Pensez-y maintenant, pourquoi feriez-vous une telle chose? Combien d'objets avez-vous douloureusement endommagés?
Autres alternatives [aussi mauvaises]
Il existe d'autres façons de mettre fin à un programme (autre que le plantage) , mais elles ne sont pas recommandées. Pour plus de précision, ils vont être présentés ici. Remarquez comment la fin normale d'un programme ne signifie pas un déroulement de la pile mais un état correct pour le système d'exploitation.
std::_Exit
provoque une interruption normale du programme, et c'est tout.
std::quick_exit
provoque un arrêt normal du programme et appelle les std::at_quick_exit
gestionnaires, aucun autre nettoyage n'est effectué.
std::exit
provoque un arrêt normal du programme et appelle ensuite les std::atexit
gestionnaires. D'autres types de nettoyage sont effectués, comme l'appel de destructeurs d'objets statiques.
std::abort
provoque une interruption anormale du programme, aucun nettoyage n'est effectué. Cela devrait être appelé si le programme s'est terminé d'une manière vraiment, vraiment inattendue. Cela ne fera que signaler au système d'exploitation la fin anormale. Certains systèmes effectuent un vidage de mémoire dans ce cas.
std::terminate
appelle le std::terminate_handler
qui appelle std::abort
par défaut.
main()
use return, dans les fonctions, utilisez une valeur de retour appropriée ou lâchez une exception appropriée. Ne pas utiliserexit()
!