6 votes

Le destructeur d'une classe C++ qui lève une exception est-il appelé ?

Supposons que j'ai une classe comme celle-ci :

#include <iostream>

using namespace std;

class Boda {
    private:
        char *ptr;

    public:
        Boda() {
            ptr = new char [20];
        }
        ~Boda() {
            cout << "calling ~Boda\n";

            delete [] ptr;
        }

        void ouch() {
            throw 99;
        }
};

void bad() {
    Boda b;
    b.ouch();
}

int main() {
    bad();
}

Il semble que le destructeur ~Boda n'est jamais appelé, donc le ptr les ressources ne sont jamais libérées.

Voici la sortie du programme :

terminate called after throwing an instance of 'int'
Aborted

Il semble donc que la réponse à ma question soit No .

Mais je pensais que la pile était déroulée lorsqu'une exception était levée ? Pourquoi n'a-t-on pas Boda b est détruit dans mon exemple ?

Veuillez m'aider à comprendre ce problème de ressources. Je veux écrire de meilleurs programmes à l'avenir.

Aussi, est-ce le soi-disant RAII ?

Merci, Boda Cydo.

9voto

Tyler McHenry Points 35551

Si l'exception n'est rattrapée nulle part, le moteur d'exécution C++ est libre d'aller directement à la fin du programme sans dérouler la pile ni appeler de destructeur.

Cependant, si vous ajoutez un bloc try-catch autour de l'appel à bad() vous verrez que le destructeur de l'objet Boda qui est appelé :

int main() {
    try {
      bad();
    } catch(...) {  // Catch any exception, forcing stack unwinding always
      return -1;
    }
}

RAII signifie que la mémoire allouée dynamiquement (tas) est toujours détenue par un objet alloué automatiquement (pile) qui la désalloue lorsque l'objet est détruit. Cela repose sur la garantie que le destructeur sera appelé lorsque l'objet alloué automatiquement sortira de sa portée, que ce soit en raison d'un retour normal ou d'une exception.

Ce comportement de cas de coin n'est normalement pas un problème en ce qui concerne RAII, puisque généralement la raison principale pour laquelle vous voulez que les destructeurs s'exécutent est de libérer de la mémoire, et toute la mémoire est rendue au système d'exploitation lorsque votre programme se termine de toute façon. Cependant, si vos destructeurs font quelque chose de plus compliqué, comme par exemple supprimer un fichier verrouillé sur le disque ou quelque chose du genre, où cela ferait une différence si le programme appelait les destructeurs ou non lors d'un crash, vous pourriez vouloir envelopper vos destructeurs dans une enveloppe. main dans un bloc try-catch qui attrape tout (pour sortir sur exception de toute façon) juste pour s'assurer que la pile se déroule toujours avant de se terminer.

2voto

kriss Points 10450

Le destructeur ne sera pas exécuté si une exception se produit dans le constructeur.

Elle sera exécutée si nécessaire (si l'exception est gérée quelque part) si l'exception est levée dans une autre méthode comme dans votre exemple. Mais comme le programme est terminé, l'appel au destructeur n'est pas nécessaire ici et le comportement dépend du compilateur...

L'idée de RAII est que le constructeur alloue des ressources et que le destructeur les libère. Si une exception se produit dans le constructeur, il n'y a pas de moyen simple de savoir quelles ressources ont été allouées et lesquelles ne l'ont pas été (cela dépend de l'endroit exact dans le constructeur où l'exception s'est produite). Vous devez également vous rappeler que si un constructeur échoue, la seule façon de le dire à l'appelant est de lever une exception. y La mémoire allouée est libérée (soit par le déroulement de la pile, soit par la mémoire allouée au tas) comme si elle n'avait jamais été allouée.

La solution est évidente : si une exception se produit dans un constructeur, il faut l'attraper et libérer les ressources allouées si nécessaire. Il se peut que du code soit dupliqué avec le destructeur, mais ce n'est pas un gros problème.

Dans le destructeur, vous ne devez pas lever d'exceptions, car cela peut entraîner de gros problèmes de déroulement de la pile.

Dans toute autre méthode, utilisez les exceptions comme vous le souhaitez, mais n'oubliez pas de les gérer quelque part. Une exception non gérée peut être pire que pas d'exception du tout. Je connais des programmes qui ne gèrent pas les exceptions pour certaines erreurs mineures... et qui plantent pour des erreurs qui ne devraient émettre qu'un avertissement.

1voto

Nikolai N Fetissov Points 52093

Essayez de vider le flux - vous verrez que le destructeur est bien appelé :

cout << "calling ~Boda" << endl;

C'est la mise en mémoire tampon des E/S qui retarde l'impression au point que la fin du programme intervient avant la sortie effective.

Edit :

Ce qui précède est valable pour exceptions traitées . Avec exceptions non gérées la norme ne précise pas si la pile est déroulée ou non. Voir également cette question SO .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X