IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Mécanisme C++ d'exceptions dans les constructeurs => inutilisable ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 82
    Par défaut Mécanisme C++ d'exceptions dans les constructeurs => inutilisable ?
    Bonjour à tous,

    Après quelques recherches sur internet, je me pose toujours une question sur le mécanisme de gestion des exceptions dans les constructeurs décrit dans le cours de Christian Casteyde : http://www.developpez.com/c/megacours/x3910.html

    J'ai considéré le cas suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
     
    #include <iostream>
     
    using namespace std;
     
    void Print(const string &inStr)
    {
            cout << inStr << endl;
    }
     
    struct A
    {
            double *valA;
     
            A()
            try : valA(0)
            {
                    valA = new double;
                    Print("A::A()");
                    throw new string("Exception !");
            }
            catch(...)
            {
                    Print("Catch de A");
                    delete valA; valA = 0; // Pas de problème: valA vaut 0 ou pointe sur une zone mémoire correctement allouée.
            }
    };
     
    struct B : public A
    {
            double *valB;
     
            B()
            try : valB(0), A()
            {
                    valB = new double;
                    Print("B::B()");
            }
            catch(...)
            {
                    Print("Catch de B");
                    if(valB) Print(string("valB != 0")) ;
                    else Print(string("valB == 0"));
     
                    delete valB; valB = 0; // Aïe, aïe ! Dans ce cas, valB n'a pas encore été initialisé à 0, il pointe vers une zone mémoire farfelue !
            }
    };
     
    int main()
    {
            try
            {
                    B b;
            }
            catch(string *inE)
            {
                    Print(*inE);
            }
    }
    A l'exécution, on obtient ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    A::A()
    Catch de A
    Catch de B
    valB != 0
    Exception !
    Dans cet exemple, le code du constructeur de B ne sera jamais exécuté et le pointeur valB n'est jamais initialisé à zéro. Pourtant, le catch de B sera exécuté. Or j'aimerais pouvoir libérer le membre valB dans ce catch. Je ne peux pas me contenter d'appeler "delete valB" sans me poser de question puisque dans le cas de cet exemple, valB n'est pas initialisé à zéro !

    En fouillant dans la FAQ, j'ai vu qu'une autre méthode (à laquelle j'avais pensé) y était proposée : http://c.developpez.com/faq/cpp/?pag...S_constructeur
    Cette second méthode marche bien : le catch d'une classe est appelé si et seulement si le constructeur de cette classe a été exécuté (éventuellement partiellement). Les classes dérivées ne seront pas averties de l'exception.

    Je me pose donc la question naturelle suivante : le C++ propose un mécanisme censé être adapté aux exceptions dans les constructeurs mais il ne me paraît pas utilisable. Est-ce que je l'utilise mal ? Comment procédez-vous pour gérer les exceptions dans les constructeurs ?

    Merci pour vos avis !

  2. #2
    Membre émérite Avatar de MatRem
    Profil pro
    Inscrit en
    Décembre 2002
    Messages
    750
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2002
    Messages : 750
    Par défaut
    Je n'ai peut être pas compris ce que tu veux faire, mais pourquoi dans le constructeur de B, tu utilises le try pour englober l'appel a A().
    En effet, si l'appel à A() à echoué, pourquoi entrer dans B()?

    En faisant ça comme ça le problème de libération de mémoire est réglé:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    #include <iostream>
     
    using namespace std;
     
    void Print(const string &inStr)
    {
            cout << inStr << endl;
    }
    
    struct A
    {
            double *valA;
     
            A()
            try : valA(0)
            {
                    valA = new double;
                    Print("A::A()");
                    throw string("Exception !");
            }
            catch(...)
            {
                    Print("Catch de A");
                    delete valA; valA = 0; // Pas de problème: valA vaut 0 ou pointe sur une zone mémoire correctement allouée.
            }
    };
    
    struct B : public A
    {
            double *valB;
     
            B()
            : valB(0), A()
    	{
    		try {
    			valB = new double;
    			Print("B::B()");
    		}
    		catch(...)
    		{
    			Print("Catch de B");
    			if(valB) Print(string("valB != 0")) ;
    			else Print(string("valB == 0"));
    	
    		delete valB; valB = 0; // Aïe, aïe ! Dans ce cas, valB n'a pas encore été initialisé à 0, il pointe vers une zone mémoire farfelue !
    		}
    	}
    };
     
    int main()
    {
            try
            {
                    B b;
            }
            catch(const string & inE)
            {
                    Print(inE);
            }
    }

    De plus il me semble que si tu déclenches une exception avec new, il faudrait libérer la mémoire, hors ça ne doit pas toujours être évident.
    C'est pourquoi je n'ai pas utiliser le new.

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 82
    Par défaut
    Ce que tu proposes est effectivement un moyen qui marche. Et je l'ai cité dans mon premier post : c'est ce qui est proposé dans la FAQ. Cette méthode présente un inconvénient : il faut penser à relancer l'exception avec un throw à la fin du catch pour que l'exception remonte dans les classes dérivées, sinon il y aura des fuites de mémoire pas forcément faciles à détecter. Dans le cas de la manière "standard", l'exception est automatiquement relancée.

    Ma question est donc de savoir à quoi sert le mécanisme proposé par le C++, lequel consiste entre autres à placer le try en tête du constructeur. S'il existe, il doit y avoir une raison et elle m'échappe.

  4. #4
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    struct A
    {
            double *valA;
     
            A()
            try : valA(0)
            {
                    valA = new double;
                    Print("A::A()");
                    throw string("Exception !");
            }
            catch(...)
            {
                    Print("Catch de A");
                    delete valA; valA = 0; // Pas de problème: valA vaut 0 ou pointe sur une zone mémoire correctement allouée.
            }
    };
    C'est pas bon ça.
    Si new lève une exception, alors il ne faut pas libérer la mémoire, tout simplement parce qu'elle n'a pas été allouée. Enfin bon delete fonctionne avec 0 donc ça marche.

    Pareil dans le second cas.

  5. #5
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 82
    Par défaut
    Evidemment que dans l'exemple que je cite, l'appel à delete n'a pas vraiment d'intérêt mais il s'agit d'un exemple pour présenter le problème. Il est en tout cas inoffensif à condition que les pointeurs pointent vers 0 ou vers une zone mémoire allouée.

    En tout cas, s'il y avait plusieurs membres alloués dynamiquement dans A et dans B, il serait bien nécessaire d'appeler delete sur les membres. Les puristes diront qu'appeler delete sur le dernier membre alloué est inutile puisque si une exception est levée, c'est qu'il n'a pas été alloué mais ce genre de discussion ne fait pas avancer le schmilblic.

    C'est étrange, j'ai l'impression que personne ne ressent ce problème avec les exceptions dans les constructeurs, c'est pourtant un problème ultra fréquent, non ? Peut-être que ma manière de présenter le problème est confuse, alors je pose la question autrement : "Comment faites-vous pour libérer la mémoire allouée dans les constructeurs ?"

  6. #6
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Citation Envoyé par dabeuliou
    Ce que tu proposes est effectivement un moyen qui marche. Et je l'ai cité dans mon premier post : c'est ce qui est proposé dans la FAQ. Cette méthode présente un inconvénient : il faut penser à relancer l'exception avec un throw à la fin du catch pour que l'exception remonte dans les classes dérivées, sinon il y aura des fuites de mémoire pas forcément faciles à détecter.
    Faux. Dans ce cas, une exception est forcément relancée à la fin du catch.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Membre confirmé
    Profil pro
    Inscrit en
    Avril 2004
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2004
    Messages : 82
    Par défaut
    Faux. Dans ce cas, une exception est forcément relancée à la fin du catch.
    J'insiste : la solution proposée par MatRem inclut le try/catch à l'intérieur du code du constructeur de B, comme dans une méthode classique. Dans ce cas, l'exception n'est PAS relancée automatiquement (ce qui parait logique à la réflexion).

    Non et je vais le lire de ce pas. Merci pour le lien.

Discussions similaires

  1. Exceptions dans le constructeur
    Par disturbedID dans le forum C++
    Réponses: 20
    Dernier message: 14/02/2008, 13h42
  2. Exception dans le constructeur
    Par olive_le_malin dans le forum C++
    Réponses: 9
    Dernier message: 24/05/2007, 18h02
  3. gérer les exceptions sur les constructeurs?
    Par LESOLEIL dans le forum Général Java
    Réponses: 9
    Dernier message: 15/03/2006, 10h46
  4. exception dans un constructeur
    Par xxiemeciel dans le forum C++
    Réponses: 25
    Dernier message: 23/11/2005, 18h14
  5. Capture d'exception dans un constructeur
    Par declencher dans le forum Composants VCL
    Réponses: 8
    Dernier message: 03/02/2004, 12h52

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo