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

Langage C++ Discussion :

Que faire contre les 'Parameter type mismatch'es ?


Sujet :

Langage C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut Que faire contre les 'Parameter type mismatch'es ?
    Bonjour,

    Je suis en C++03 et j'ai un code comme ceci :

    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
     
    // Fonctions de la bibliothèque que j'utilise
    virtual uint16_t TextArea::getTextWidth() const;
    virtual void Drawable::setX(int16_t x);
    virtual void Drawable::setWidth(int16_t width;
     
     
    // Creation de mes drawables
    Box background_m;
    TextArea text_m;
     
     
    // Code utilisant tout ce beau monde
    enum
    {
    BACKGROUND_BORDER = 10
    };
     
    int16_t bw = text_m.getTextWidth() + BACKGROUND_BORDER * 2;
    background_m.setX(width / 2 - bw / 2);
    background_m.setWidth(bw);
    Mon IDE CLion analyses le code à la volée (avec clang j'ai l'impression) et me lève un warning comme ceci sur l'appel à setX():
    Warning:(39, 23) Parameter type mismatch: Values of type 'int' may not fit into the receiver type 'int16_t'
    Que faire dans pareil cas ?

    Merci pour vos conseils !

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 963
    Points
    32 963
    Billets dans le blog
    4
    Par défaut
    Salut,

    static_cast
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Clang a raison : tu essayes de faire entrer une valeur de type int (classiquement codé sur 4 bytes) dans une valeur codée sur 2 bytes. Voilà qui pourrait poser bien des problèmes, indépendamment du fait que l'on sait pertinemment bien que BACKGROUND_BORDER est "dans les limites" (une question que je me pose avec ton code: "what if" getTextWidth() venait à renvoyer une valeur égale à std::numeric_limits<int16_t>::max()-5 Tu serais un peu dans la merde, non ).

    L'idéal, bien sur, serait de pouvoir modifier la signature de setWidth pour qu'elle accepte un int (qui devrait d'ailleurs être non signé, au passage), histoire de n'avoir plus de problème à ce sujet ou (mais ce n'est possible qu'en C++11 et ultérieur, donc impossible pour toi) de définir le type de l'énumération comme étant de type int16_t. Mais je me doute bien qu'il n'est pas en ton pouvoir de le faire

    Du coup, la solution de Bousk est bel et bien la seule solution possible : dire à clang que tu sais que les types ne correspondent pas, mais que "tu sais ce que tu fais" (en priant pour que ce soit effectivement le cas, cf la question que je posais plus haut), et qu'il peut sans problème faire la conversion.

    Juste un truc au passage: C++11 est sorti il y a maintenant six ans. C++14 est sorti depuis trois ans, et il est supporté par tous les compilateurs récents. Je me doute bien que tu n'as "pas voix au chapitre" sur ce coup, mais tu n'envisagerais pas de "donner un coup de pied dans la fourmilière" pour inciter les décideurs à mettre leurs outils à jour
    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

  4. #4
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Salut,

    Tu peux convertir tes entiers en int16_t avec boost::numeric_cast :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <boost/numeric/conversion/cast.hpp>
     
    namespace DrawableUtil
    {
        template<class IntegerType>
        void setX(Drawable& drawable, IntegerType x)
        {
            drawable.setX(boost::numeric_cast<int16_t>(x));
        }
    }
    Si l'argument est en dehors des limites de int16_t, cela lancera une exception boost::numeric::positive_overflow ou boost::numeric::negative_overflow.

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    En fait, je n'étais qu'à moitié sérieux quand je posais la question d'un éventuel dépassement de valeur, car, vu que l'on parle d'une fenêtre à l'écran, même en comptant en pixels en HD, on devrait toujours être dans une zone de sécurité à ce sujet.

    Et puis, je suis plutôt d'avis qu'il faudrait une assertion et non une exception pour ce genre de dépassement, car c'est clairement le genre de chose auquel le développeur doit apporter une solution en cas d'erreur
    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

  6. #6
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Et puis, je suis plutôt d'avis qu'il faudrait une assertion et non une exception pour ce genre de dépassement, car c'est clairement le genre de chose auquel le développeur doit apporter une solution en cas d'erreur
    A mon avis, dans la plupart des cas de ce genre, des assertions ne serviraient à rien, à part encombrer le code.
    En effet, admettons que l'on ait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <limits>
     
    namespace DrawableUtil
    {
        template<class IntegerType>
        void setXAvecPrecondition(Drawable& drawable, IntegerType x)
        {
            assert(x >= std::numeric_limits<int16_t>::min());
            assert(x <= std::numeric_limits<int16_t>::max());
            drawable.setX(static_cast<int16_t>(x));
        }
    }
    Si on a des préconditions, c'est pour qu'elles soient contrôlées dans le code appelant.
    Or, que fera le code appelant quand un argument sera trop petit ou trop grand ? Il lancera une exception.
    Comme le code qui lancera cette exception sera toujours le même, il sera factorisé, par exemple comme ceci :
    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
    #include <boost/numeric/conversion/cast.hpp>
    #include <limits>
     
    namespace DrawableUtil
    {
        template<class IntegerType>
        void setXAvecPrecondition(Drawable& drawable, IntegerType x)
        {
            assert(x >= std::numeric_limits<int16_t>::min());
            assert(x <= std::numeric_limits<int16_t>::max());
            drawable.setX(static_cast<int16_t>(x));
        }
     
        template<class IntegerType>
        void setX(Drawable& drawable, IntegerType x)
        {
            static_cast<void>(boost::numeric_cast<int16_t>(x));
            setXAvecPrecondition(drawable, x);
        }
    }
    Le code appelant appellera directement DrawableUtil::setX plutôt que DrawableUtil::setXAvecPrecondition. Ainsi, on reviendra au point de départ :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <boost/numeric/conversion/cast.hpp>
     
    namespace DrawableUtil
    {
        template<class IntegerType>
        void setX(Drawable& drawable, IntegerType x)
        {
            drawable.setX(boost::numeric_cast<int16_t>(x));
        }
    }
    On pourrait m'objecter que ce n'est pas DrawableUtil::setX qui devrait lancer l'exception, mais le code appelant, afin que le message de l'exception contienne des informations contextuelles.
    Mais les deux idées sont compatibles. Si on veut concevoir un programme avec une gestion d'erreurs sophistiquée, ce que l'on peut faire, c'est travailler avec des types d'exception sophistiqués qui ont des constructeurs capables de capturer l'état de la pile et des fonctions pour accumuler des informations contextuelles.
    Alors, dans DrawableUtil::setX, on aurait un code du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <limits>
     
    namespace DrawableUtil
    {
        template<class IntegerType>
        void setX(Drawable& drawable, IntegerType x)
        {
            if(x < std::numeric_limits<int16_t>::min())
                throw MaSuperExceptionEntierTropPetit(x, std::numeric_limits<int16_t>::min());
            if(x > std::numeric_limits<int16_t>::max())
                throw MaSuperExceptionEntierTropGrand(x, std::numeric_limits<int16_t>::max());
            drawable.setX(static_cast<int16_t>(x));
        }
    }
    Et, dans le code appelant, on aurait quelque chose du genre :
    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
    try {
        [...]
            // code qui appelle DrawableUtil::setX et d'autres fonctions qui peuvent lever
            // plein de types d'exceptions différents
     
    } catch(MaSuperException& e) { // MaSuperException est une classe de base abstraite.
        e.ajouterInformationContextuelle("blababla");
        throw; // Bonne nouvelle : le type dynamique de l'exception n'est pas perdu.
    } catch(...) {
        std::exception_ptr eptr = std::current_exception();
        const std::string infoContextuelle = "blababla";
        convertirEnMaSuperException_puis_ajouterInformationContextuelle_puis_repropager(eptr, infoContextuelle);
            // La fonction ci-dessus factorise un râteau de catch.
            // Le type dynamique de l'exception MaSuperException propagée dépend du type
            // de l'exception capturée dans eptr.
    }

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Si on a des préconditions, c'est pour qu'elles soient contrôlées dans le code appelant.
    Or, que fera le code appelant quand un argument sera trop petit ou trop grand ? Il lancera une exception.
    Pas forcément, justement.

    Le développeur reste seul juge de la politique à appliquer en cas de valeurs hors limites:
    On obtient une taille négative le développeur peut décider de la forcer à 0 ou à XXX (selon son bon vouloir). On obtient une valeur excessive idem

    Le fait est surtout que pour moi, il n'y a rien à faire : une exception ne devrait être lancée que lorsque l'on identifie une situation à laquelle l'utilisateur d'une fonctionnalité peut s'attendre, mais sur laquelle il n'a absolument aucun contrôle (ex: un serveur down, un fichier qui ne respecte pas le format prescrit, un appel système qui échoue, ...).

    A l'opposé de cela, tu as la programmation par contrat, principalement représentée par les différents types d'assertions dont on dispose, qui nous dit que nous obtiendront le résultat prévisible et reproductible attendu et valide si nous fournissons des données valides. Or, fournir des données valides à une fonction, c'est du seul ressort de celui qui utilise la fonction en question; et c'est à lui seul de décider ce qu'il convient de faire s'il se rend compte que les données sont invalides / corrompues. Les assertions font alors office de dernier rempart permettant de se rendre compte que, dans une circonstance bien particulière, l'utilisateur d'une fonction a oublié de valider correctement les données transmises

    Mais bon, je crois que l'on s'écarte pas mal du sujet de la question là
    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

  8. #8
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Finalement, j'ai changé d'avis. La programmation par contrat est adapté aussi à ce genre de cas.

    Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void foo() {
        ...
        const int entierSaisi = getEntierSaisiParLUtilisateurDansUneSpinBox();
        assert(entierSaisi >= cVALEUR_MINIMALE_SAISISSABLE); // post-condition
        assert(entierSaisi <= cVALEUR_MAXIMALE_SAISISSABLE); // post-condition
        static_assert(cVALEUR_MINIMALE_SAISISSABLE >= std::numeric_limits<int16_t>::min()) // pour garantir que entierSaisi >= std::numeric_limits<int16_t>::min()
        static_assert(cVALEUR_MAXIMALE_SAISISSABLE <= std::numeric_limits<int16_t>::max()) // pour garantir que entierSaisi <= std::numeric_limits<int16_t>::max()
        DrawableUtil::setXAvecPrecondition(drawable, entierSaisi);
        ...
    }

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Tu m'en vois ravi

    Est ce mon intervention sur les cas d'utilisation des exceptions qui t'a fait changer d'avis
    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

  10. #10
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    C'est bien ton message d'hier qui m'a fait changé d'avis, même si je ne suis pas 100% d'accord avec le passage « une exception ne devrait être lancée que lorsque l'on identifie une situation à laquelle l'utilisateur d'une fonctionnalité peut s'attendre, mais sur laquelle il n'a absolument aucun contrôle (ex: un serveur down, un fichier qui ne respecte pas le format prescrit, un appel système qui échoue, ...) ».

    Au départ, le raisonnement que j'avais fait était le suivant : Comme les overflows et underflows sont très rares en convertissant des entiers en int16_t, il vaut mieux écrire un code court et simple avec boost::numeric_cast<int16_t> que d'écrire des préconditions comme entier <= std::numeric_limits<int16_t>::max() qui ont tendance à se propager des fonctions appelées vers des fonctions appelantes. Plus le code est simple, plus il évolue facilement.

    Après ton message d'hier, j'ai commencé à préparer une contre-argumentation en développant mon raisonnement ci-dessus. Mais, en réfléchissant à la programmation par contrats, à un moment, je me suis dit que, dans le code déjà existant, on pourrait avoir déjà indirectement la garantie que entier <= std::numeric_limits<int16_t>::max(), par exemple si entier a une valeur constante petite connue à la compilation ou si entier <= uneCertaineConstante && uneCertaineConstante <= std::numeric_limits<int16_t>::max(). Idem pour std::numeric_limits<int16_t>::min(). Dans ces cas-là, on appellerait directement DrawableUtil::setXAvecPrecondition sans passer par un contrôle explicite qui lancerait une exception si l'entier est hors des limites de int16_t.

    Cela dit, si on se penche sur le code de Bktero :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int16_t bw = text_m.getTextWidth() + BACKGROUND_BORDER * 2;
    background_m.setX(width / 2 - bw / 2);
    je pense que se serait une mauvaise idée de complexifier le code ainsi :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    int16_t bw = text_m.getTextWidth() + BACKGROUND_BORDER * 2;
    constexpr int16_t cMIN_THEORICAL_bw = BACKGROUND_BORDER * 2;
    constexpr int16_t cMAX_THEORICAL_bw = cMAX_TEXT_WIDTH + BACKGROUND_BORDER * 2;
    const int x = width / 2 - bw / 2;
    constexpr int cMIN_THEORICAL_x = cMIN_THEORICAL_width / 2 - cMAX_THEORICAL_bw / 2;
    constexpr int cMAX_THEORICAL_x = cMAX_THEORICAL_width / 2 - cMIN_THEORICAL_bw / 2;
    static_assert(cMIN_THEORICAL_x >= std::numeric_limits<int16_t>::min());
    static_assert(cMAX_THEORICAL_x <= std::numeric_limits<int16_t>::max());
    background_m.setX(static_cast<int16_t>(x));
    Donc, dans un cas comme celui-ci, soit on omet volontairement de vérifier les préconditions :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int16_t bw = text_m.getTextWidth() + BACKGROUND_BORDER * 2;
    DrawableUtil::setXAvecPrecondition(background_m, width / 2 - bw / 2);
    soit on fait un contrôle très simple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int16_t bw = text_m.getTextWidth() + BACKGROUND_BORDER * 2;
    background_m.setX(boost::numeric_cast<int16_t>(width / 2 - bw / 2));

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    En fait, ce que tu dois comprendre, c'est qu'il est particulièrement rare que tu doive tester à la fois la précondition et la postcondition sur une même valeur: si ta précondition est respectée, tu tourne les choses comme tu veux, mais tu dois avoir un résultat valide et correct.

    Car le fait est que la valeur que tu fournis à setWidth est forcément connue au niveau de la fonction qui y fait appel. Le cas le plus simple étant que le développeur l'appelle avec une constante quelconque, sous une forme proche de
    Le cas un peu plus complexe venant du fait qu'il peut utiliser la largeur d'un des élément dont il a connaissance (mais qui respecte lui-même la précondition), et il devra alors le faire sous une forme qui serait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    auto newWidth = a.getWidth() + constante;
    if(newWidth < MAXWIDTH)
        b.setWidth(newWidth);
    Enfin, il y a le cas le plus complexe: la nouvelle valeur pour la largeur vient d'une source à laquelle on ne peut -- a priori -- pas faire confiance. parce que c'est une entrée utilisateur, par ce que cette valeur est lue dans un fichier quelconque ou parce qu'on l'obtient -- de manière générale -- "de n'importe quelle manière depuis l'extérieur".

    A ce moment là, la précondition de setWidth() devient... la postcondition de la fonction qui nous permet de l'obtenir, et le code appelant de setWidth ressemblerait à quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    auto newWidth = readFromFile("myConfig.xml"/*, sans doute un autre paramètre pour savoir quelle valeur on souhaite*/);
    b.setWidth(newWidth);
    Dans cette situation particulière, le type qui fait appel à setWidth() n'a absolument aucun aucune garantie que readFromFile renverra une valeur qui respectera la précondition de setWidth: cette garantie ne pourra être obtenue que si... le développeur de readFromFile impose la précondition de setWidth comme une postcondition de sa propre fonction.

    Mais voilà: readFromFile utilise une donnée externe issue d'un fichier. Le développeur de readFromFile peut donc se trouver confronté à quantité de raisons pour lesquelles la précondition de setWidth (et donc la postcondition de readFromFile) ne sera pas respectée. Parmi celles-ci on peut au moins citer les classiques:
    1. Parce que le format du fichier lu est inconsistant;
    2. parce que la valeur obtenue est (largement ) supérieure à la valeur maximale acceptée par setWidth.


    Pire encore: le développeur de readFromFile ne peut absolument rien faire pour arranger les choses. Tout ce qu'il peut faire, c'est lancer une exception, car, si il laisse l'application continuer avec la valeur obtenue, il sait que l'application sera dans un état inconsistant.

    Le seul truc, c'est que tu peux avoir trois développeurs différents :
    1. un qui s'occupe de la fonction readFromFile(), qui n'a aucune raison de savoir à quel endroit cette fonction sera appelée, ni même pour quelle raison;
    2. un qui s'occupe de setWidth(), qui ne fait même pas partie du projet, vu qu'il fait partie de l'équipe qui développe ta bibliothèque IHM, et qui a encore moins de raisons de savoir à quel endroit setWidth est appelée et pour quelle raison et
    3. un qui appelle setWidth() et peut être readFromFile juste avant et qui sait quand il utilise ces fonctions, et pourquoi.


    Hé bien, dans ce genre de situaiton, chaque développeur est responsable de la sécurité qu'il peut apporter au code qu'il écrit lui-même:
    • le développeur de setWidth() doit imposer une précondition sur la valeur à attribuer à width au travers d'une (ou de plusieurs) assertions,
    • le développeur de readFromFile() doit imposer une postcondition sur la valeur qui sera renvoyée par la fonction au travers d'une exception si elle n'est pas respectée et
    • le développeur de la fonction qui fait appel à setWidth doit pouvoir réagir au fait que readFromFile lancera peut-être une exception en choisissant ce qu'il veut faire à ce moment là (peut être en la laissant remonter jusqu'à un point où une autre fonction pourra corriger le tir) et il doit, d'un autre coté, pouvoir garantir (par programmation, et donc sans doute par un test) que la précondition de setWidth sera respectée.

    Bien sur, le troisième développeur n'est même pas obligé de faire appel à readFromFile.

    Quand j'ai écrit mon livre, luc et moi avons eu une discussion au sujet des pré et post conditions. Il s'est rallié à mon idée et en a tiré un ticket de blog qui explique cela bien mieux que moi ici
    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

  12. #12
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 963
    Points
    32 963
    Billets dans le blog
    4
    Par défaut
    Il me semblait évident que la version longue de ma réponse (que j'ai supprimée/simplifié) était "static_cast, éventuellement en vérifiant les bornes"
    Il y a de nombreux cas où la valeur ne peut de toutes évidences pas dépasser les bornes (la taille d'une fenêtre en pixel, ça rentrera toujours dans un uint16; si tu cherches la petite bête du cas où la fenêtre est anormalement grande : on ne parle plus d'un débutant et j'attends pas qu'une telle personne demande de l'aide aussi simple)

    Et son code ne nécessitera surement jamais plus qu'un static_cast sans vérification
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    virtual uint16_t TextArea::getTextWidth() const; 
    enum
    {
    BACKGROUND_BORDER = 10
    };
     
    int16_t bw = text_m.getTextWidth() + BACKGROUND_BORDER * 2;
    background_m.setX(width / 2 - bw / 2);
    background_m.setWidth(bw);
    on a donc width qui est un int16, bw un int16 : soit la division par int transforme l'opérande en int, soit la soustraction transforme le tout en int, mais pour qu'une telle opération passe hors bornes d'un int16, faut s'accrocher

    Ensuite il y a de très nombreux cas, de nombreux corps de métier, où lancer une exception est incorrecte/mal vue/une mauvaise idée : j'élimine par défaut tout code qui lance sciemment une exception. Une exception, dans mon secteur, ça reste un truc exceptionnel (cap'tain obvious) absolument imprévisible ou une erreur de prog/pointeur dans la majorité des cas : on remonte les premières afin de créer un dump ou notifier le joueur/constructeur et corrige les secondes.

    Pire encore: le développeur de readFromFile ne peut absolument rien faire pour arranger les choses. Tout ce qu'il peut faire, c'est lancer une exception, car, si il laisse l'application continuer avec la valeur obtenue, il sait que l'application sera dans un état inconsistant.
    Ou juste ne rien faire et enlever une fonctionnalité, surtout s'il s'agit d'une nouvelle fonctionnalité et donc que l'appli était dans un état correct auparavant et sans ça.
    Il vaut mieux avoir un bouton qui ne fait rien, ou qui ne s'affiche pas, que de faire crasher toute l'appli parce qu'un paramètre est daubé au startup (des mois de bataille avec la team UI parce qu'ils lancent une exception quand un paramètre est moisi et font crasher tout le jeu.. alors que putain il suffit de ne pas afficher le bouton pour qu'absolument tout le monde puisse continuer à travailler sur l'ensemble du reste du projet et qu'ils corrigent ça dans leur coin sans boycotter toute l'équipe)

    Et s'il s'agit d'un guignol qui change un paramètre critique de l'appli qui est absolument nécessaire à son initialisation : une erreur de ce niveau ne devrait jamais sortir de sa machine et surtout pas être commit et propagée à l'équipe
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  13. #13
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Citation Envoyé par Bousk Voir le message
    on a donc width qui est un int16, bw un int16 : soit la division par int transforme l'opérande en int, soit la soustraction transforme le tout en int, mais pour qu'une telle opération passe hors bornes d'un int16, faut s'accrocher
    Il est vrai que je n'avais pas remarqué que le code de Bktero suggérait que width était de type int16_t, même si on ne voyait pas la déclaration.
    Dans ce cas, en effet, si width et bw sont tous les deux des int16_t, donc avec des valeurs entre -215 et 215-1, alors width / 2 - bw / 2 a forcément une valeur entre -215+1 et 215-1, donc ne franchit pas les limites de int16_t.

  14. #14
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Ou juste ne rien faire et enlever une fonctionnalité, surtout s'il s'agit d'une nouvelle fonctionnalité et donc que l'appli était dans un état correct auparavant et sans ça.
    Il vaut mieux avoir un bouton qui ne fait rien, ou qui ne s'affiche pas, que de faire crasher toute l'appli parce qu'un paramètre est daubé au startup (des mois de bataille avec la team UI parce qu'ils lancent une exception quand un paramètre est moisi et font crasher tout le jeu.. alors que putain il suffit de ne pas afficher le bouton pour qu'absolument tout le monde puisse continuer à travailler sur l'ensemble du reste du projet et qu'ils corrigent ça dans leur coin sans boycotter toute l'équipe)
    Sur ce point, je suis d'accord.

    Mais imagine que le fichier requis soit "simplement" inaccessible pour une raison qui n'a rien à voir avec les développeurs (tu remarqueras d'ailleurs que c'étaient bel et bien les deux cas envisagé dans mon intervention) et, pire encore, que cela survienne en production Que vas-tu faire

    Laisser croire à l'utilisateur que la ressource a été chargée sans problème jusqu'à ce qu'il se rende compte que ce n'est pas le cas annuler toutes les fonctionnalités qui ont besoin de cette ressource quoi d'autre

    Si tu choisi d'annuler toutes les fonctionnalités rendues inaccessibles par l'absence de la ressource, comment vas-tu savoir qu'elle est inaccessible, si ce n'est en lançant une exception

    Je suis tout à fait d'accord avec toi et le captain obvious: une exception doit être exceptionnelle. Mais ca ne veut pas dire que l'on doit partir du principe qu'elle ne surviendra jamais ...

    Parce que, soyons honnêtes: A priori, le format du fichier que nous essayons de lire est clairement défini, et le fichier n'a donc aucune raison valable de ne pas le respecter. Et a priori, il n'y a aucune raison valable pour qu'il contienne des valeurs aberrantes... Sauf que...
    • Sauf qu'un imbécile peut décider de supprimer ce fichier par mégarde
    • sauf qu'un imbécile peut s'amuser à aller modifier ce fichier "au petit bonheur"


    sauf qu'un imbécile peut... faire tant de choses "inavouables" avec ce fichier qu'on ne peut jamais le prendre pour "argent comptant", même si ce fichier est sensé avoir été généré à l'aide d'outils qui garantissent son bon fonctionnement...

    Pourquoi Parce que c'est un fichier. Et qu'un fichier doit être considéré avec la même suspicion que n'importe quelle entrée utilisateur. Et que l'on ne peut jamais faire confiance aux entrées utilisateur.
    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

  15. #15
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 469
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 469
    Points : 6 102
    Points
    6 102
    Par défaut
    Citation Envoyé par koala01 Voir le message
    sauf qu'un imbécile peut... faire tant de choses "inavouables" avec ce fichier qu'on ne peut jamais le prendre pour "argent comptant", même si ce fichier est sensé avoir été généré à l'aide d'outils qui garantissent son bon fonctionnement...
    Un fichier peut aussi être corrompu parce qu'une copie s'est mal passée (ex : coupure de courant pendant la copie du fichier). Ce n'est pas toujours la faute de l'utilisateur ou d'un bogue.

  16. #16
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Un fichier peut aussi être corrompu parce qu'une copie s'est mal passée (ex : coupure de courant pendant la copie du fichier). Ce n'est pas toujours la faute de l'utilisateur ou d'un bogue.
    tout à fait, mais le problème reste le mêm: si, pour une raison ou une autre, un fichier (ou toute autre ressource, d'ailleurs) ne contient pas ce qu'il devrait dans le format qu'il le devrait, cela sort complètement du champs de responsabilité du développeur d'une fonctionnalité qui aurait pour objectif d'en extraire les données.

    A partir de là, il n'y a qu'une seule certitude: le comportement ne peut pas réussir; la fonctionnalité ne peut pas être menée à termes.

    Et ce genre d'échec qui n'est absolument pas du à une erreur de codage, il n'y a qu'une seule solution pour l'empêcher de se propoager : lancer une exception.

    Après, cela ne veut pas dire qu'il faille la laisser remonter au point de planter l'application... S'il y a moyen de "moyenner" avec cet échec avant, autant essayer de le récupérer. Mais cela ne se fait certainement pas au niveau de la fonction elle-même
    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

  17. #17
    Membre habitué
    Homme Profil pro
    007
    Inscrit en
    Octobre 2014
    Messages
    119
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : 007

    Informations forums :
    Inscription : Octobre 2014
    Messages : 119
    Points : 188
    Points
    188
    Par défaut
    Citation Envoyé par Bktero Voir le message
    Je suis en C++03 et j'ai un code comme ceci :
    T'es vraiment en C++03 ??? ou C++03TR1 ?
    D'où sort-il ton int16_t ? Tout ça me laisse perplexe. Mon avis est que tu
    compiles déjà, et au minimum, en C++11.

    Essaye de voir ce que ça donne en typant ton énumération :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    enum : int16_t {BACKGROUNG_BORDER= 10};
    ou mieux, faire un "#pragma message" sur "__cplusplus".

    +/- HS:
    Un fichier de configuration, pour moi, doit être validé au plus tôt et au court
    du démarrage de l'application. Donc, très en amont. Parce que charger et
    contrôler les données au fur et à mesure des besoins alors que l'application
    est déjà bien avancée dans le "runtime", ça ne laisse présager rien de bon
    pour l'utilisateur final.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Êtes-vous pour ou contre les "strict type hints" ?
    Par RideKick dans le forum Langage
    Réponses: 44
    Dernier message: 21/03/2012, 21h18
  2. [CS3] Que faire quand les templates ne mettent plus a jour ?
    Par holala! dans le forum Dreamweaver
    Réponses: 1
    Dernier message: 06/05/2008, 07h05
  3. [AJAX] Que faire contre les fuites mémoires (memory leaks)
    Par cassy dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 21/08/2007, 16h50
  4. Que faire lorsque les performances d'une base chute ?
    Par Doctor Z dans le forum Oracle
    Réponses: 11
    Dernier message: 16/02/2005, 14h38

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