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 :

à propos de "l'idiome GetLastError"


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 288
    Billets dans le blog
    2
    Par défaut à propos de "l'idiome GetLastError"
    Bonjour,

    j'ai commencé le dev en entreprise avec la MFC. J'ai mis longtemps à m'en remettre et ré-apprendre à coder correctement en c++, mais j'en garde des séquelles. Notemment cet "idiome du GetLastError" que j'utilise toujours, et même de plus en plus fréquemment.

    Ce que j'entends par "idiome du GetLastError", c'est une chaîne de carractère en membre privé d'une classe, avec un accesseur, dans laquelle on stocke un message d'erreur lorsqu'une erreur est survenue lors d'un traitement au sein de cette classe. L'idée est qu'ainsi les fonctions membres de cette classe ne renvoient qu'un booléen (true si ok, false si une erreur est survenue), et lorsque l'utilisateur de cette classe veut savoir ce qu'il s'est passé, il appelle cette fonction GetLastError().

    Un peu de code pour illustrer:
    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
    class MaClasse
    {
    public:
        bool FaitQuelqueChose(); // return true si ok, false si erreur
        inline const std::string & GetLastError() const { return last_error_; }
    private:
        mutable std::string last_error_; //mutable car on veut pouvoir gérer l'erreur même dans les fonctions constantes
    };
     
    int main()
    {
       MaClasse ma_classe;
       if ( !ma_classe.FaitQuelqueChose() )
       {
          // traitement de l'erreur en utilisant ma_classe.GetLastError();
       }
       //...
    }
    Le truc c'est que je vois beaucoup d'avantages, mais pas d'inconvénients. Les avantages sont:
    . indépendance de cette classe avec le système de log de l'application (qui peut être complexe parfois)
    . simplification de l'interface: les fonctions susceptibles de générer des erreurs ne renvoient qu'un simple booleen.
    . avec cette technique, pas besoin de remonter les exceptions.
    . simplification d'utilisation de la classe : l'utilisateur de cette classe ne s'occupe des erreurs que s'il en a besoin.

    Mais comme je ne vois pas beaucoup d'utilisation de cet idiome (à part dans la MFC), je suppose qu'il doit y avoir de nombreux inconvénients. Savez-vous quels sont-ils?

  2. #2
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Sans réflexion approfondie, voilà ce que je réponds/


    . simplification de l'interface: les fonctions susceptibles de générer des erreurs ne renvoient qu'un simple booleen.
    1) impossibilité à ta fonction de retourner un objet. Ca peut rendre bancale une architecture

    2) Tu n'as pas forcément la "vraie" erreur dans le cas d'appel en cascade.
    Ex

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Foo
    {
    std::string erreur;
    public:
    bool f1(){ if(machin){erreur="machin dans f1"; else return true;}
    bool f2() ( //f1 fait un truc mais tu ne vois pas l'erreur tout de suite
    if(bidule) erreur="erreur dans f2"; //bidule depend de l'action de f1
    } 
    //si tu fais un appel à f2 depuis l'extérieur, tu auras la valeur d'erreur de  f2.
    };
    3) Obligation de tester tout les retours, niveau lourdeur et redondance de code c'est sympa ....

    . indépendance de cette classe avec le système de log de l'application (qui peut être complexe parfois)
    Enfin ton loggeur devra avoir accès à la classe pour récupérer la valeur de la chaîne d'erreur. Avec ta technique, ta gestion d'erreur empiète sur le rôle de la classe alors que ces deux notions sont normalement orthogonales.

    . avec cette technique, pas besoin de remonter les exceptions.
    Les exceptions permettent une gestion non nominale de la progression de façon automatisée. Avec ton code, quand tu détectes une erreur, bah tu fais quoi ?

    simplification d'utilisation de la classe : l'utilisateur de cette classe ne s'occupe des erreurs que s'il en a besoin.
    Une erreur devrait toujours être signalée et traitée
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  3. #3
    Membre très actif Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Par défaut
    Je préfère l'idiome dit du try catch switch GetLastError return ERROR_CODE

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    try {
      //
    }
    catch (...) {
      switch (GetLastError()) {
        case TYPE_ERREUR_N1: return ERROR_FAIL;
        default: throw;

  4. #4
    Membre émérite
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    780
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mai 2006
    Messages : 780
    Par défaut
    C'est du errno local à chaque classe? mais j'imagine que ça a les mêmes problèmes, en cas de multithread aussi c'est pas la fête non?

    Perso j'ai pas de problème à utiliser des exceptions/ messages d'erreurs dans les logs/à l'écran.

  5. #5
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 288
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par nikko34 Voir le message
    C'est du errno local à chaque classe? mais j'imagine que ça a les mêmes problèmes, en cas de multithread aussi c'est pas la fête non?
    Quels peuvent être les problèmes en multi-thread?

  6. #6
    Alp
    Alp est déconnecté
    Expert confirmé

    Avatar de Alp
    Homme Profil pro
    Inscrit en
    Juin 2005
    Messages
    8 575
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations forums :
    Inscription : Juin 2005
    Messages : 8 575
    Par défaut
    Citation Envoyé par r0d Voir le message
    Quels peuvent être les problèmes en multi-thread?
    Si plusieurs threads utilisent la classe qui a le get last error là, un thread peut appeler GetLastError et récupérer l'erreur liée à un autre thread parce que l'autre thread entre temps a utilisé ton objet et le get last error a été modifié... ce genre de choses. Donc à moins que tu mette un gros lock sur tuot le code, ce qui va ralentir abominablement l'exécution et bloquer pas mal de threads...

    Je préfère encore une approche fortement orientée exceptions ou un truc à base de boost::variant<résultat_normal, type_identifiant_l'erreur> (à la Either en Haskell) donc en gros quelque chose qui se repose plus sur les types / le compilateur. En C++ on n'a pas que l'OO, faut pas l'oublier

  7. #7
    Membre éclairé
    Inscrit en
    Avril 2005
    Messages
    1 110
    Détails du profil
    Informations forums :
    Inscription : Avril 2005
    Messages : 1 110
    Par défaut
    Ces fonctions qui renvoient un code d'erreur incitent le programmeur à tester systématiquement ce code d'erreur. Bonjour la lisibilité du code bourré de "if" après chaque appel de fonction. Et ça ne le rend pas plus fiable pour autant. Que du contraire, le programmeur a la responsabilité de prendre une décision pour gérer l'erreur, comment garantir qu'il fera le bon choix ?
    Et d'ailleurs dans le "else" il fera quoi ? Dire boujour sur la console pour expliquer que "le disque est bourré à mort", ou envoyer une exception pour dire la même chose dans le main juste avant la sortie du prg

    Les fonctions de l'API Windows par exemple renvoient un code d'erreur de manière quasi systématique. Chaque fois que je regarde les exemples de code pour illustrer une utilisation, les trois-quarts concerne la gestion du code retour...
    OK pour admettre qu'une API se débarasse de cette responsabilité de gérer les erreurs. Mais si à chaque niveau de couche logicielle on renvoit la responsabilité à la couche supérieur, il y aura un lampiste au bout du code qui va souffrir parce qu'on lui impose un rôle qui n'est pas le sien. Et tout ça rien que pour gérer des erreurs, c'est à dire des situations anormales et rares, exceptionnelles devrait-on dire. Il n'y a pas le mot "exception" là-dedans ?

    Je préfère une approche "transactionnelle" de la gestion d'erreur. Les exceptions y aide grandemment, et sont même faites pour cela en C++ il me semble.

  8. #8
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 288
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 288
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par Davidbrcz Voir le message
    1) impossibilité à ta fonction de retourner un objet. Ca peut rendre bancale une architecture
    Si je veux retourner un objet, j'utilise un paramètre passé par référence. D'ailleurs en règle générale c'est pas terrible qu'une fonction retourne directement un objet car ça fait une copie.

    Citation Envoyé par Davidbrcz Voir le message
    2) Tu n'as pas forcément la "vraie" erreur dans le cas d'appel en cascade.
    Oui effectivement, ça c'est un vrai problème. C'est d'ailleurs explicite dans le nom de la fonction: GetLastError. on récupère la dernière erreur.

    Citation Envoyé par Davidbrcz Voir le message
    3) Obligation de tester tout les retours, niveau lourdeur et redondance de code c'est sympa ....
    Ben non pas forcément. Seules les fonctions susceptible de générer des erreurs auront leur résultat testé. Et ça, je ne vois pas comment l'éviter de toutes façon. A moins de faire un gros bloc try, mais c'est pas terrible.

    Citation Envoyé par Davidbrcz Voir le message
    Enfin ton loggeur devra avoir accès à la classe pour récupérer la valeur de la chaîne d'erreur. Avec ta technique, ta gestion d'erreur empiète sur le rôle de la classe alors que ces deux notions sont normalement orthogonales.
    Ben de toutes façons, c'est soit la classe qui accède au logger, soit l'inverse. Je ne vois pas en quoi l'un est meilleur que l'autre.

    Citation Envoyé par Davidbrcz Voir le message
    Les exceptions permettent une gestion non nominale de la progression de façon automatisée. Avec ton code, quand tu détectes une erreur, bah tu fais quoi ?
    Comprends pô

    Citation Envoyé par Davidbrcz Voir le message
    Une erreur devrait toujours être signalée et traitée
    Mmhh... c'est la première fois que j'entends cela. C'est peut-être ce qu'on enseigne en théorie, mais dans la pratique ce n'est tout simplement pas possible, et rarement recommandable. Dès qu'une applicatin commence à être un peu complexe, il y a tout un tas d'erreur qu'il est totalement inutile de gérer. Par exemple je sais pas, on recherche un objet dans un conteneur dans le but de le supprimer et on ne le trouve pas alors qu'il devrait y être. Franchement, 9 fois sur 10, on ne fait rien dans ce cas là.
    Après ça dépend de ce qu'on appelle une erreur.

  9. #9
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Si je veux retourner un objet, j'utilise un paramètre passé par référence. D'ailleurs en règle générale c'est pas terrible qu'une fonction retourne directement un objet car ça fait une copie.
    Oopa

    Ben non pas forcément. Seules les fonctions susceptible de générer des erreurs auront leur résultat testé. Et ça, je ne vois pas comment l'éviter de toutes façon. A moins de faire un gros bloc try, mais c'est pas terrible.
    Bah en théorie, toutes les fonctions sont susceptibles de générer des erreurs, que ca soit dans l'acquisition des ressources, dans le processus de traitement, ...
    Comprends pô
    En cas d'erreur, avec une exception, le flux des instructions est interrompu et on remonte de gestionnaire d'exception en gestionnaire jusqu'à soit en trouver un qui marche ou fermer le programme. Avec ta méthode, quand tu trouves une erreur tu fais quoi ? Tu continues sachant qu'il y a eu une erreur?
    Après ça dépend de ce qu'on appelle une erreur.
    Toutafé
    Mais bon, ton exemple, tenter de supprimer un truc qui n'est pas présent, me laisse perplexe.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  10. #10
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par r0d Voir le message
    Mais comme je ne vois pas beaucoup d'utilisation de cet idiome (à part dans la MFC), je suppose qu'il doit y avoir de nombreux inconvénients. Savez-vous quels sont-ils?
    J'ai l'impression que ce genre d'approche est limitée à des classes ayant un petit nombre d'instances, et une durée de vie assez longue. Dans les autres cas :

    Pour de petites classes, il va y avoir un surcout lié à la création d'une chaine pour chaque instance.

    Si ces classes ont vocation à être copiées (si par exemple tu les utilises dans des conteneurs STL, c'est copie garantie dans ces cas là), il va falloir décider si la dernière erreur doit ou non être copiée... Normalement, la réponse est probablement non, ce qui signifie un constructeur de copie et un opérateur copie à faire à la main. (tu peux peut être éviter cela en encapsulant ton message d'erreur dans un type qui gère cela tout seul, mais bon...)

    Plus généralement, il faut absolument "consommer l'erreur" dès qu'elle est détectée... Les problèmes liés à l'éventuelle copie de ton instance font qu'un "vieux" LastError peut être très difficile à interpréter.

    Enfin, la gestion des erreurs se trouve alors au niveau de l'instance de chaque classe. Ca ne marchera bien que pour des erreurs "propres à l'instance": n'a pas pu s'initialiser, n'a pas réussi à faire telle action, etc... pour des messages d'erreur provenant de l'extérieur de l'instance (réseau en panne, donnée en entrée erronnée), ca peut ne pas convenir.

    En résumé, je crois que c'est une bonne approche pour des classes assez statiques, et qui fournissent des "services" (comme les composants d'une API d'OS en fait...), mais elle ne peut servir de "méthode standard" de gestion d'erreur.

    Francois

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