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 :

échec dans un constructeur


Sujet :

C++

  1. #1
    Membre éprouvé
    Profil pro
    Inscrit en
    Décembre 2004
    Messages
    1 299
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Décembre 2004
    Messages : 1 299
    Par défaut échec dans un constructeur
    Bonjour, ma question est assez simple : comment détecter si la création d'une instance de ma classe s'est bien passée ? Je prends l'exemple d'une classe qui a un pointeur pour lequel je dois teste si l'allocation mémoire s'est bien passée. Je suis allé sur la FAQ (http://cpp.developpez.com/faq/cpp/in...S_constructeur) mais ceci ne réponde que partiellement à mon problème. Je m'explique, supposons que j'ai le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    void maFonction()
    {
        Test test(3,5); // je reprends l'exemple du lien que j'ai donné ci-dessus
        cout << test.getTableau1().size(); // j'invente le getter qui renvoie le tableau 1
    }
    Imaginons que la création de la classe ait planté. Comment le savoir ? Ma méthode consiste à chaque fois à rajouter un

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    bool ok // true si la création s'est bien passée, false sinon
    pour savoir si tout se passe bien, et mon exemple deviendrait (j'ai modifié le prototype de ma fonction)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    int maFonction()
    {
        Test test(3,5); // je reprends l'exemple du lien que j'ai donné ci-dessus
        if(! test.getOK())
          return 1; // erreur
     
        cout << test.getTableau1().size(); // j'invente le getter qui renvoie le tableau 1
     
        return 0; // aucune erreur
    }
    C'est un peu lourd. Y a-t-il un moyen pour arrêter le programme si une erreur a été commise ?

    Merci d'avance

  2. #2
    Membre confirmé
    Inscrit en
    Juin 2009
    Messages
    33
    Détails du profil
    Informations personnelles :
    Âge : 33

    Informations forums :
    Inscription : Juin 2009
    Messages : 33
    Par défaut
    Bonjour,

    pour résoudre ton problème, je pense qu'il suffit d'écrire :

    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
    void maFonction()
    {
       try
       {
          Test test(3,5);
          cout<<test.getTableau1().size();
       }
       catch (const std::bad_alloc & )
       {
          cout<<"Creation de l'objet 'test' echouee !!!";
          throw; // on fait remonter l'exception si on ne sait pas ce qu'il faut faire dans ce cas
       }
     
    // Puis dans la fonction appelante (par exemple le main)
    int main()
    {
       ...
       ...
       try
       {
          maFonction();
       }
       catch(const std::bad_alloc & )
       {
          return EXIT_FAILURE; // par exemple si on ne peut vraiment rien faire pour réparer le problème
       }
       ...
       ...
    }
    Ton code ressemblerait à ça. En gros il faut faire remonter l'exception jusqu'à une fonction capable de prendre la bonne décision ("Faut-il réessayer en modifiant quelque chose ?" ...)

    Sinon pour arrêter le programme sans faire remonter l'exception, il faudrait utiliser 'exit(EXIT_FAILURE);' dans 'maFonction', mais alors là, bonjour les fuites de mémoire ...

    En espérant avoir résolu un problème.

  3. #3
    Membre Expert
    Avatar de Joel F
    Homme Profil pro
    Chercheur en informatique
    Inscrit en
    Septembre 2002
    Messages
    918
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur en informatique
    Secteur : Service public

    Informations forums :
    Inscription : Septembre 2002
    Messages : 918
    Par défaut
    http://www.gotw.ca/gotw/066.htm

    Sutter propose plusieurs guidelines intéressantes à ce sujet

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 636
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 636
    Par défaut
    Salut,

    L'explication de Getrix est relativement bonne, ceci dit, il faut juste ajouter qu'il ne faut rattraper une exception que... s'il y a quelque chose à faire au moment où on la récupère, et la relancer si... il y a encore autre chose à faire par la suite

    Par exemple, imaginons une classe dont le constructeur prend la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    MaClass::MaClass(size_t s):size_(s)
    {
        ptr = new Type[s]; // lance std::bad_alloc si l'allocation échoue
    }
    qui est, entre autre, appelée par une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    void foo(/*...*/)
    {
        /* rien n'a été fait avant ... en tout cas, l'état du système n'a pas
         * été modifié
         */
        MaClass c(126543987);
        /* il y a peut être des choses à faire après */
    }
    Le système est (sensé être) dans un état cohérent avant que l'on appelle le constructeur de MaClass.

    Rien n'indique que nous soyons en mesure de faire quoi que ce soit pour... permettre au constructeur de travailler correctement (c'est à dire que nous soyons en mesure de libérer suffisamment de mémoire contigüe pour permettre au constructeur d'allouer correctement la mémoire)

    Comme la fonction n'a rien fait (comprend: n'a pas modifié l'état du système), et qu'elle n'a, malheureusement, rien à faire si l'exception est lancée, il n'y a aucun intérêt à récupérer l'exception ici si c'est pour... la renvoyer sans rien avoir fait pour la gérer.

    Maintenant, si nous avons une autre fonction qui prend la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void resize(std::vector<MaClass> & tab, size_t nbr)
    {
        if(nbr >tab.size())
        {
            /* suppression des derniers éléments */
        }
        else
        {
            /*ajouter des éléments : */
            while(tab.size()!=nbr)
                tab.push_back(MaClass(tab.size());
        }
    }
    Le problème auquel on est confronté ici, c'est que, si nous voulons que notre tableau contienne un nombre donné (nbr) d'éléments, c'est pour une raison a priori valable.

    Seulement, chaque ajout d'un élément peut parfaitement lancer une exception

    Les questions qu'il faut donc se poser sont:
    • Peut-on faire quelque chose pour y remédier A priori, je répondrais non
    • Peut-on continuer avec un nombre inférieur d'objets, et que faut-il faire pour remettre le système dans un état cohérent (prenant entre autre le nombre d'éléments réels) la réponse dépendra ici de la raison pour laquelle on voulait faire en sorte d'avoir nbr élément
    • L'exception lancée contient-elle suffisamment d'information de contexte pour nous permettre d'essayer d'apporter une solution "plus tard" la réponse est clairement non: nous risquons d'avoir besoin d'informations complémentaires (telles que le nombre d'éléments de départ, le nombre d'éléments souhaités et le nombre d'éléments réellement obtenu, par exemple).
    • ... j'en oublie peut être.
    Nous constatons donc ici que nous devons récupérer l'exception afin d'y rajouter des informations de contexte et de la relancer par la suite.

    Mais cela implique aussi que les fonctions qui appelleront bar (de manière directe ou indirecte) devront, selon leur capacité à apporter tout ou partie de la solution, éventuellement récupérer l'exception, la gérer pour la partie qu'elles savent, et éventuellement rajouter des informations de contexte dont elles sont les seules à disposer, afin de permettre aux fonctions appelantes d'essayer d'apporter à leur tour tout ou partie de la solution.

    Si, parce que cela peut arriver, une fonction arrive à résoudre entièrement le problème ayant provoqué l'exception, il suffira donc de lui faire attraper et gérer l'exception, et de ne pas lui faire la renvoyer.
    Au final, tu peux parfaitement avoir quatre cas de figures:
    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
     // aucune gestion de l'exception
    void foo()
    {
        MaClass c(123456789);
    }
    // renvoi d'une exception plus précise
    void bar()
    {
        try
        {
            MaClass(c123456789)
        }
        catch(std::bad_alloc & e)
        {
     
            throw ErreurDinitialisation(123456789);
        }
    }
    // gestion (au minimum) partielle du problème et renvoi d'une exception
    void foobar()
    {
        try
        {
            bar();
        }
        catch(ErreurDinitialisation &e )
        {
            /* 1- correction de ce que l'on peut
             * 2- renvoi de l'exception pour la suite du traitement 
             */
            throw e;
        }
    }
    /* gestion "finale" du problème */
    void doSomething()
    {
        try
        {
            foobar();
        }
        catch(ErreurDinitialisation &e)
        {
            /*gestion du problème, mais aucune exception n'est relancée */
        }
        /* on continue l'exécution "comme si de rien n'était */
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. exception dans un constructeur
    Par xxiemeciel dans le forum C++
    Réponses: 25
    Dernier message: 23/11/2005, 18h14
  2. Réponses: 3
    Dernier message: 06/11/2005, 18h02
  3. Réponses: 1
    Dernier message: 06/11/2005, 17h55
  4. [debutant] rappel de la classe dans le constructeur
    Par newtito dans le forum Débuter
    Réponses: 6
    Dernier message: 05/10/2005, 00h15
  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