web-dev-qa-db-fra.com

Comment mettre fin au code C ++

J'aimerais que mon code C++ cesse de fonctionner si une certaine condition est remplie, mais je ne sais pas comment faire. Donc, à tout moment, si une instruction if est vraie, terminez le code comme suit:

if (x==1)
{
    kill code;
}
243
The Nightman

Il existe plusieurs manières, mais vous devez d’abord comprendre pourquoi le nettoyage d’objet est important, et donc la raison std::exit est marginalisé par les programmeurs C++.

RAII et dénouement de pile

C++ utilise un idiome appelé RAII , qui signifie simplement que les objets doivent effectuer l'initialisation dans le constructeur et le nettoyage dans le destructeur. Par exemple, la classe std::ofstream [peut] ouvrir le fichier au cours du constructeur, puis l'utilisateur effectue des opérations de sortie sur celui-ci, puis à la fin de son cycle de vie, généralement déterminé par son étendue. , 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 n'écrira peut-être pas toutes les données qu'il était censé écrire dans le fichier.

Par exemple considérons ce code

_#include <fstream>
#include <exception>
#include <memory>

void inner_mad()
{
    throw std::exception();
}

void mad()
{
    std::unique_ptr<int> ptr(new 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, afin qu'elle sache la fin du cycle de vie de os, appelant ainsi son destructeur et effectuant un nettoyage approprié par fermer et rincer le fichier sur le disque.
  • Possibilité 2: Le lancement d'une exception prend également en compte le cycle de vie des objets dans le périmètre actuel, en effectuant ainsi le nettoyage approprié ...
  • Possibilité 3: Ici, le déroulement de la pile entre en action! Même si l'exception est levée à _inner_mad_, le dérouleur passera par la pile de mad et main pour effectuer un nettoyage approprié, tous les objets vont être détruits correctement, y compris ptr et os.
  • Possibilité 4: Eh bien, ici? exit est une fonction C qui n’est ni consciente ni compatible avec les idiomes C++. Il n'effectue pas de nettoyage de vos objets, y compris os dans la même étendue. Donc, votre fichier ne sera pas fermé correctement et pour cette raison, le contenu pourrait ne jamais y être écrit!
  • Autres possibilités: Il ne vous reste plus qu'à laisser la portée principale, en effectuant une implicite _return 0_ et ayant ainsi le même effet que la possibilité 1, c'est-à-dire nettoyer.

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 une exception.

Moyens possibles pour End

Retour de main!

Vous devriez le faire autant 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 éventuellement 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 renvoyer zéro ou EXIT_SUCCESS pour signaler que le programme s'est terminé avec succès et EXIT_FAILURE pour signaler le programme terminé sans succès, any toute autre forme de valeur de retour est définie par la mise en œuvre (§18.5/8).

Cependant, vous risquez de perdre beaucoup de temps dans la pile d’appels, et le renvoi de tout cela peut être douloureux ...

[Ne pas] lancer une exception

Le lancement d'une exception effectuera un nettoyage d'objet approprié à l'aide du dépilage de pile, en appelant le destructeur de chaque objet de la portée précédente.

Mais voici la prise ! Son implémentation est définie 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 fonction noexcept dans le milieu de la pile d'appels. Ceci est indiqué dans §15.5.1 [except.terminate]:

  1. Dans certaines situations, la gestion des exceptions doit être abandonnée pour des techniques de traitement des erreurs moins subtiles. [Note: Ces situations sont:

    [...]

    - lorsque le mécanisme de traitement des exceptions ne parvient pas à trouver un gestionnaire pour une exception levée (15.3) ou lorsque la recherche d'un gestionnaire (15.3) rencontre le bloc le plus externe d'une fonction avec une spécification noexcept qui ne permet pas l'exception (15.4), ou [...]

    [...]

  2. Dans de tels cas, std :: terminate () est appelé (18.8.3). Dans la situation où aucun gestionnaire correspondant n'est trouvé, il est défini par l'implémentation que la pile soit ou non déroulée avant l'appel de std :: terminate () [...]

Nous devons donc l'attraper!

Ne jetez une exception et attrapez-la à main!

Etant donné que les exceptions non capturées ne peuvent pas effectuer le déroulement de la pile (et par conséquent ne procéderont pas au nettoyage correct), nous devrions intercepter l'exception dans main, puis renvoyer un statut de sortie ( EXIT_SUCCESS ou EXIT_FAILURE ).

Donc, une bonne installation serait:

_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 actif de la pile n'appelle son destructeur respectif pour effectuer le nettoyage.

Ceci est appliqué dans §3.6.1/4 [basic.start.init]:

Terminer le programme sans quitter le bloc actuel (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 mettre fin à un programme lors de la destruction d'un objet avec une durée de stockage statique ou de thread, le comportement du programme n'est pas défini.

Pensez-y maintenant, pourquoi voudriez-vous faire une chose pareille? Combien d'objets avez-vous douloureusement endommagé?

Autres [aussi mauvaises] alternatives

Il existe d'autres moyens de terminer un programme (autre que le blocage), mais ils ne sont pas recommandés. Juste pour clarifier, ils vont être présentés ici. Remarquez comment fin normale du programme ne signifie pas que la pile se déroule, mais un état okay pour le système d'exploitation .

  • std::_Exit provoque une interruption normale du programme, et c'est tout.
  • std::quick_exit provoque une interruption normale du programme et appelle std::at_quick_exit gestionnaires, aucun autre nettoyage n'est effectué.
  • std::exit provoque une interruption normale du programme, puis appelle std::atexit gestionnaires. D'autres types de nettoyage sont effectués, tels que l'appel de destructeurs d'objets statiques.
  • std::abort provoque un arrêt anormal du programme, aucun nettoyage n'est effectué. Ceci devrait être appelé si le programme s'est terminé d'une manière vraiment, vraiment inattendue. Cela ne fera que signaler à l'OS la fin anormale. Certains systèmes effectuent un vidage mémoire dans ce cas.
  • std::terminate appelle le std::terminate_handler qui appelle std::abort par défaut.
408
Denilson Amorim

Comme Martin York l'a mentionné, la sortie n'effectue pas le nettoyage nécessaire, contrairement au retour.

Il est toujours préférable d'utiliser return dans le lieu de sortie. Au cas où vous ne seriez pas dans la rue principale, retournez-y en premier.

Prenons l'exemple ci-dessous. Avec le programme suivant, un fichier sera créé avec le contenu mentionné. Mais si return est commenté et non commenté exit (0), le compilateur ne vous assure pas que le fichier aura le texte requis.

int main()
{
    ofstream os("out.txt");
    os << "Hello, Can you see me!\n";
    return(0);
    //exit(0);
}

Pas seulement cela, avoir plusieurs points de sortie dans un programme rendra le débogage plus difficile. Utilisez exit uniquement lorsque cela peut être justifié.

59
Narendra N

Appelez la fonction std::exit .

37
Otávio Décio

Les gens disent "appelez l'exit (code de retour)", mais c'est une mauvaise forme. Dans les petits programmes, ça va, mais cela pose un certain nombre de problèmes:

  1. Vous finirez par avoir plusieurs points de sortie du programme
  2. Cela rend le code plus compliqué (comme utiliser goto)
  3. Il ne peut pas libérer la mémoire allouée à l'exécution

Vraiment, le seul moment où vous devriez sortir du problème est avec cette ligne dans main.cpp:

return 0;

Si vous utilisez exit () pour gérer les erreurs, vous devez en savoir plus sur les exceptions (et les exceptions d'imbrication), en tant que méthode beaucoup plus élégante et sûre.

23
jkeys

return 0; mettez cela où vous voulez dans int main() et le programme se fermera immédiatement.

14
Evan Carslake

Si vous rencontrez une erreur au fond du code, émettez une exception ou définissez le code d'erreur. Il est toujours préférable de lancer une exception au lieu de définir des codes d'erreur.

11
Jagannath

Renvoyez une valeur de votre main ou utilisez la fonction exit. Les deux prennent un int. Quelle que soit la valeur renvoyée, la valeur renvoyée n'a pas vraiment d'importance.

11
Goz

Le programme se terminera lorsque le flux d'exécution atteindra la fin de la fonction principale.

Pour terminer avant cela, vous pouvez utiliser la fonction exit (int status), où status est une valeur renvoyée à celui qui a démarré le programme. 0 indique normalement un état sans erreur

11
chrisbunney

En général, vous utiliseriez la méthode exit() avec un état de sortie approprié .

Zéro signifie une course réussie. Un statut différent de zéro indique qu'un problème quelconque s'est produit. Ce code de sortie est utilisé par les processus parents (scripts Shell, par exemple) pour déterminer si un processus a été exécuté avec succès.

9
Brian Agnew

Au-delà de l'appel exit (code_erreur) - qui appelle des gestionnaires atexit, mais pas des destructeurs RAII, etc. - de plus en plus, j'utilise des exceptions.

De plus en plus mon programme principal ressemble à

int main(int argc, char** argv) 
{
    try {
        exit( secondary_main(argc, argv );
    }
    catch(...) {
        // optionally, print something like "unexpected or unknown exception caught by main"
        exit(1);
    }
}

où secondary_main se trouve dans où tous les éléments qui étaient à l'origine sont placés - c'est-à-dire que le principal d'origine est renommé secondary_main et que le talon principal ci-dessus est ajouté. Ceci est juste une finesse, de sorte qu'il n'y ait pas trop de code entre le plateau et la prise principale.

Si vous voulez, attrapez d'autres types d'exceptions.
J'aime bien capturer les types d'erreur de chaîne, comme std :: string ou char *, et les imprimer dans le gestionnaire de captures dans main.

L'utilisation d'exceptions comme celle-ci permet au moins d'appeler les destructeurs RAII, afin qu'ils puissent effectuer le nettoyage. Ce qui peut être agréable et utile.

Globalement, la gestion des erreurs C (sorties et signaux) et la gestion des erreurs C++ (exceptions try/catch/throw) s’exécutent au mieux, de manière incohérente.

Ensuite, où vous détectez une erreur

throw "error message"

ou un type d'exception plus spécifique.

7
Krazy Glew