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 :

Redéfinition de méthode et principe de Liskov


Sujet :

C++

  1. #41
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Pour ça, je dirais plutôt "seulement s'il est marqué dans le contrat qu'on les accepte".

    Si le contrat de la classe de base spécifie que la classe dérivée peut ajouter des pré-conditions, OK. Sinon, la classe dérivée doit fonctionner sans pré-conditions autres que la classe de base.
    Là dessus, nous sommes bien d'accord...

    Mais si le comportement "protégé" par les conditions est polymorphe, on *risque* de devoir aussi modifier les conditions, et il est alors *peut-être* nécessaire de prévoir que les conditions en question le soient aussi...

    Mais ca, ca doit être déterminé en phase de conception (et il faut que l'implémentation s'y tienne)
    Citation Envoyé par 3DArchi Voir le message
    Si on reprend :
    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 A
    {
    public :
    virtual void foo();[pré-condition p]
    }
    class B :public A
    {
    public :
    virtual void foo();[pré-condition p' plus restrictive]
    }
     
    void Fonction(A& a)
    [Pré-condition : p]
    {
      ASSERT(p(a));
       a.foo();
    }
    Tu ne peux substituer un B à A car tu risque d'avoir p' non rempli.
    Tu donnes le GO alors que l'objet n'est pas cohérent.
    En fait, si j'ai bien compris, en restreignant la pré-condition, tu romps le contrat, donc tu n'a plus B IS-A A.
    A vrai dire, je ne le coderais pas ainsi...

    Pour moi, il n'y a rien qui puisse mieux être en mesure de dire qu'un objet est dans un état cohérent que... l'objet lui-même...

    Si l'on modifie le comportement d'un type dérivé, comme je l'ai indiqué, il est *peut-être* nécessaire de modifier le comportement qui... vérifie que cet objet est cohérent.

    C'est pour cela que je préfèrerais fortement coder la fonction sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void fonction(A& a)
    {
        if(a.precheck())
        {
            /* ok, on y va */
        }
        if(!a.postcheck())
        {
            /* ca a foiré, que faire pour récupérer a dans un état cohérent? */
        }
    }
    ou, si condition lance une exception, qui pourrait aller jusqu'à une forme proche de
    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
    void fonction(A& a)
    {
        try
        {
            a.precheck(); //pré condition
            a.go(); // action
            a.postchech() // post condition
        }
        catch(PreConditionFailed & e)
        {
        }
        catch(PostConditionFailed &e)
        {
            /* quel comportement appler pour le "roll back" ? */
        }
    }
    de manière à déléguer pleinement la responsabilité de vérifier les conditions à l'objet lui-même...

    Partant de là, s'il advient que le comportement doit etre polymorphe, ou que les condition doivent (et peuvent) l'être, la fonction s'adaptera du mieux possible à l'ensemble des possibilités
    Citation Envoyé par white_tentacle Voir le message
    Je crois que tu as manqué juste trois petites choses :
    1. l'appelant doit être en mesure de vérifier le contrant avant d'appeler la fonction
    2. le comportement du programme final (non buggé) doit être le même qu'on active la vérification de contrat ou pas
    3. dans la programmation par contrat, le LSP s'applique bien sur toutes les fonctions polymorphes
    Justement, c'est à l'objet de déterminer s'il est dans un état cohérent, ce qui permet - le cas échéans - à l'appelant de vérifier si les conditions sont remplies avant l'appel, mais ce qui peut permettre de créer une fonction qui s'en chargera

    En déléguant à l'objet la responsabilité de vérifier s'il est dans un état cohérent, tu gagne énormément de souplesse vis à vis de l'héritage et tu t'assure très facilement qu'une vérification apportera une réponse correcte, quelles que puissent-être les évolutions apportées au contrat

    Pour le 3, je dirais qu'il faut bien comprendre que, si tu as une fonction polymorphe, c'est le comportement de l'objet réellement en cours de traitement qu'il faut prendre en compte...

    A partir de là, nous sommes bien d'accord sur le fait que la logique doit rester cohérente, et qu'il ne faut pas que ce comportement polymorphe renvoye faux alors qu'il aurait du renvoyer vrai... Là où nous ne sommes semble-t-il pas d'accord, c'est sur le fait que les conditions puissent évoluer, dans n'importe quel sens, en fonction du comportement polymorphe
    Après, ça ne veut pas dire que c'est la seule manière de faire, on est bien d'accord . Mais quand on sort de ce cadre, ça n'est plus de la programmation par contrats.
    Si, si les conditions particulières, qui doivent de toutes manières prévaloir sur les conditions générales sont correctement énoncées et appliquées
    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

  2. #42
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par koala01 Voir le message
    C'est pour cela que je préfèrerais fortement coder la fonction sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void fonction(A& a)
    {
        if(a.precheck())
        {
            /* ok, on y va */
        }
        if(!a.postcheck())
        {
            /* ca a foiré, que faire pour récupérer a dans un état cohérent? */
        }
    }
    ou, si condition lance une exception, qui pourrait aller jusqu'à une forme proche de
    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
    void fonction(A& a)
    {
        try
        {
            a.precheck(); //pré condition
            a.go(); // action
            a.postchech() // post condition
        }
        catch(PreConditionFailed & e)
        {
        }
        catch(PostConditionFailed &e)
        {
            /* quel comportement appler pour le "roll back" ? */
        }
    }
    de manière à déléguer pleinement la responsabilité de vérifier les conditions à l'objet lui-même...
    Mais là tu ne respecte plus le point 2 qu'il donne : ça ne marche pas si tu désactive la vérification du contrat.
    [EDIT]: les 3 conditions font que tu ne peux pas échapper : les pré-conditions ne peuvent pas être plus restrictives et les post conditions ne peuvent pas être plus lâches. Ou sinon, tu ne fais plus de programmation par contrat au sens où il l'a énoncé.

  3. #43
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Mais là tu ne respecte plus le point 2 qu'il donne : ça ne marche pas si tu désactive la vérification du contrat.
    [EDIT]: les 3 conditions font que tu ne peux pas échapper : les pré-conditions ne peuvent pas être plus restrictive et les post conditions ne peuvent pas être plus lâche. Ou sinon, tu ne fais plus de programmation par contrat au sens où il l'a énoncé.
    Pourquoi ca ne marcherait pas

    Je vais prendre un exemple, pour me faire comprendre:

    Soient 4 classes, A, B, C et D telles que:
    • A est la classe de base des trois autres
    • B spécialise la classe A (conditions plus restrictives)
    • C généralise la classe A (conditions plus permissives)
    • D réalise la classe A (conditions et comportement identiques)

    Nous nous trouvons face à un code proche de
    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
    class A
    {
        public:
            virtual bool pre() const{/* vérification des conditions "de base" */}
            virtual bool post() const{/* vérification après "de base" */}
            virtual void go() /* const */{/* comportement "de base" */}
            virtual void rollBack(){/* remise en état "de base" */}
    };
    class B: public A
    {
        public:
            virtual bool pre() const
            {
                /* vérification des conditions plus restrictives, adaptées
                 * au comportement réel d'un B
                 */
            }
            virtual bool post() const
            {
                /* vérification des conditions plus restrictives, adaptées
                 * au comportement réel d'un B
                 */
            }
            virtual void go() /* const */
            {/* comportement "adapté" */}
            virtual void rollBack(){/* remise en état adaptée à B */}
    };
     
    class C: public A
    {
        public:
            virtual bool pre() const
            {
                /* vérification des conditions plus laxistes, adaptées
                 * au comportement réel d'un C
                 */
            }
            virtual bool post() const
            {
                /* vérification des conditions plus laxistes, adaptées
                 * au comportement réel d'un C
                 */
            }
            virtual void go() /* const */
            {/* comportement "adapté" */}
            virtual void rollBack(){/* remise en état adaptée à C */}
    };
     
    class D: public A
    {
        public:
            /* les différents comportements prévus pour A me satisfont
             * tout à fait, merci
             */
    };
    Avec les fonctions:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    void fooCertified(A & a)
    {
        if(a.pre())
            a.go();
        if(!a.post())
            a.rollBack();
    }
    void fooUncertified(A& a)
    {
        a.go();
    }
    et L'utilisation
    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
     
    int main()
    {
        A a;
        B b;
        C c;
        D d;
        #if defined(DDEBUG)
            fooCertified(a);
            fooCertified(b);
            fooCertified(c);
            fooCertified(d);
        #else
            fooUnCertified(a);
            fooUnCertified(b);
            fooUnCertified(c);
            fooUnCertified(d);
        #elseif
    }
    Tu as un comportement identique que la vérification du contrat soit activée ou non, et tes conditions sont respectées, qu'il s'agisse des conditons générales ou des conditions particulières, que ces conditions particulières soient plus restrictives ou plus laxistes que les conditions générales

    Honnêtement, je ne vois absolument pas où il pourrait y avoir un problème

    Et mieux, nous pourrions mettre la gestion de la vérification du contrat directement dans la fonction, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void autoFunction(A& a)
    {
        #if defined(DDEBUG)
        if(a.pre())
        #endif
            a.go();
        #if defined(DDEBUG)
        if(!a.post())
            a.rollBack()
        #endif
    }
    EDIT et si tu veux la version qui lance des exceptions, c'est relativement facile
    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. #44
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Rapidement car j'ai plus le temps ce soir
    Tu mets la condition int i>=0 dans les préconditions de A
    Tu mets la condition int i>0 dans les préconditions de B

    Si tu active les vérifications, tu peux substituer un B à A car ta fonction de vérification de la pré-condition va t'indiquer STOP.
    Si tu désactive les vérifications, alors tu va exécuter ta fonction alors que tu ne le souhaites pas
    --> Tu as violé LSP puisque tu ne peux pas substituer un B à A si tu désactive tes vérifications.

  5. #45
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Tu as un comportement identique que la vérification du contrat soit activée ou non, et tes conditions sont respectées, qu'il s'agisse des conditons générales ou des conditions particulières, que ces conditions particulières soient plus restrictives ou plus laxistes que les conditions générales
    En l'occurrence, non, le comportement n'est pas le même.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void fooCertified(A & a)
    {
        if(a.pre())
            a.go();
        if(!a.post())
            a.rollBack();
    }
    void fooUncertified(A& a)
    {
        a.go();
    }
    Avec le A suivant (buggé, c'est fait exprès ) :

    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
    class A
    {
    public:
       A(int p) : maval(p) {}
       int maval;
       bool pre() { return p >= 0; }
       bool post() { return p>= 0; }
       void go() { p = sqrt(p); }
    }
     
    int main()
    {
       A a(-3);
       fooCertified(a); // pas de problème !
       fooUncertified(a); // plantage !
    }

  6. #46
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Rapidement car j'ai plus le temps ce soir
    Tu mets la condition int i>=0 dans les préconditions de A
    Tu mets la condition int i>0 dans les préconditions de B

    Si tu active les vérifications, tu peux substituer un B à A car ta fonction de vérification de la pré-condition va t'indiquer STOP.
    Non, ma méthode de vérification est virtuelle et la fonction qui appelle la vérification utilise une référence.

    Ce sera donc toujours la version correspondante au type réel de l'objet qui sera appelée, et donc:
    1. la vérification s'effectuera selon les règles édictées pour le type réel
    2. l'exécution s'effectuera également selon les règles édictées pour le type réel
    3. la substitution est pleinement respectée, pour autant que les comportements (éventuellement adaptés) pre et post n'aillent pas renvoyer vrai alors qu'ils devaient renvoyer faux selon les règles (plus laxistes ou plus strictes) en vigueur pour le type réel

    Si tu désactive les vérifications, alors tu va exécuter ta fonction alors que tu ne le souhaites pas
    je vais appeler le comportement adapté au type réel de mon objet, ainsi que je le souhaite
    --> Tu as violé LSP puisque tu ne peux pas substituer un B à A si tu désactive tes vérifications.
    Je n'ai absolument pas violé LSP car le comportement (qui, je le redis, doit rester cohérent) réagit exactement comme on s'y attend, parce qu'il est adapté au type de l'objet réellement utilisé
    Citation Envoyé par white_tentacle Voir le message
    En l'occurrence, non, le comportement n'est pas le même.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void fooCertified(A & a)
    {
        if(a.pre())
            a.go();
        if(!a.post())
            a.rollBack();
    }
    void fooUncertified(A& a)
    {
        a.go();
    }
    Avec le A suivant (buggé, c'est fait exprès ) :

    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
    class A
    {
    public:
       A(int p) : maval(p) {}
       int maval;
       bool pre() { return p >= 0; }
       bool post() { return p>= 0; }
       void go() { p = sqrt(p); }
    }
     
    int main()
    {
       A a(-3);
       fooCertified(a); // pas de problème !
       fooUncertified(a); // plantage !
    }
    Oui, mais si tu modifie volontairement un code correct pour le bugger, tu peux même en arriver à obtenir 3 pour 1+1
    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

  7. #47
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Le problème, c'est que je ne suis pas sûr qu'un contrat ait le droit d'être virtuel, alors que c'est le cas dans ton code...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  8. #48
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Oui, mais si tu modifie volontairement un code correct pour le bugger, tu peux même en arriver à obtenir 3 pour 1+1
    C'est vrai . Mais l'idée, c'était de montrer que ton code n'a pas le même comportement, si tu vérifies les préconditions ou pas.

    J'ai un peu abusé en prenant directement un exemple où le contrat n'était pas respecté. Mais si tu change le contrat pour être plus restrictif en entrée, c'est ce qu'il se passe via un appel polymorphe.

  9. #49
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Le problème, c'est que je ne suis pas sûr qu'un contrat ait le droit d'être virtuel, alors que c'est le cas dans ton code...
    Là, on apporte un élément neuf qui est de nature à empêcher la mise en oeuvre de la logique implacable que j'énonce

    Mais il peut être reste possible de travailler avec une interface non virtuelle qui prendrait (en m'amusant avec des conditions qui lancent des exception) et en gardant les quatre classe exposées plus haut 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
    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
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    class A
    {
        public:
            void perform()
            {
                try
                {
                    pre();
                    go();
                    post();
                }
                catch(BadPreCondition& e)
                {
                    /* rien à faire ici */
                }
                catch(BadPostCondition &e)
                {
                    rollBack();
                }
            }
        protected:
            virtual void pre() const
            {
                #if defined(DDEBUG)
                /* lance BadPostCondition si condition non remplie */
                #endif
            }
            virtual void post() const
            {
                #if defined(DDEBUG)
                /* lance BadPostCondition si condition non remplie */
                #endif
            }
            virtual void go()
            {
                /* comportement par défaut */
            }
            virtual void rollBack()
            {
                /* remise en état par défaut */
            }
    };
    class B: public A
    {
     
        protected:
            virtual void pre() const
            {
                #if defined(DDEBUG)
                /* lance BadPostCondition si condition non remplie, avec  
                 * adaptation à B (plus restrictives) */
                #endif
            }
            virtual void post() const
            {
                #if defined(DDEBUG)
                /* lance BadPostCondition si condition non remplie, 
                 * avec adaptation à B (plus restrictives) */
                #endif
            }
            virtual void go()
            {
                /* comportement adapté à un B*/
            }
            virtual void rollBack()
            {
                /* remise en état adaptée à un B */
            }
    };
    class C: public A
    {
     
        protected:
            virtual void pre() const
            {
                #if defined(DDEBUG)
                /* lance BadPostCondition si condition non remplie, avec  
                 * adaptation à C (plus laxistes) */
                #endif
            }
            virtual void post() const
            {
                #if defined(DDEBUG)
                /* lance BadPostCondition si condition non remplie, 
                 * avec adaptation à C (plus laxistes ) */
                #endif
            }
            virtual void go()
            {
                /* comportement adapté à un C */
            }
            virtual void rollBack()
            {
                /* remise en état adaptée à un C */
            }
    };
    class D :public A
    {
        /* le comportement adapté pour un A me va très bien, merci ;) */
    };
    Cela aurait même le bénéfice de déléguer la responsabilité de l'exécution à la classe

    Et, du fait de la compilation conditionnelle de pre et de post, au pire, nous nous permettons de récupérer une exception qui ne sera jamais lancée

    L'utilisation devient encore plus simple, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main()
    {
        A a;
        B b;
        C c;
        D d;
        a.perform();
        b.perform();
        c.perform();
        d.perform();
        return 0;
    }
    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. #50
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 518
    Points
    41 518
    Par défaut
    Mais les fonctions de vérifications sont toujours virtuelles.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  11. #51
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    C'est vrai . Mais l'idée, c'était de montrer que ton code n'a pas le même comportement, si tu vérifies les préconditions ou pas.

    J'ai un peu abusé en prenant directement un exemple où le contrat n'était pas respecté. Mais si tu change le contrat pour être plus restrictif en entrée, c'est ce qu'il se passe via un appel polymorphe.
    Ah, non...

    De la manière dont je présente les choses (et sous réserve qu'un contrat puisse être virtuel, sinon, tout s'effondre), nous envisageons l'adaptation du contrat à la situation propre rencontrée du fait du comportement polymorphe.

    Ce que je veux dire, c'est que nous avons déterminé en période de conception que, lorsque l'on appel le comportement go, il est susceptible de travailler avec des restrictions différentes selon le type réellement utilisé.

    De là, nous avons décidé de permettre d'adapter le contrat afin de prendre ces différences de restrictions en compte (des conditions "particulières"), qu'elles soient plus laxistes ou plus restrictives.

    Le code que je présente se contente d'activer ou de désactiver la vérification du contrat (et ce qui découle de cette vérification), mais il respecte donc pleinement ton point 2

    J'admet cependant que j'aurais pu provoquer un affichage quelconque indiquant le non respect du contrat dans la version "certifiée"

    Par contre, ce que toi tu montre, c'est une classe dans laquelle la conception n'a mis en évidence aucune nécessité de polymorphisme...
    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. #52
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    Mais les fonctions de vérifications sont toujours virtuelles.
    Je m'en rend malheureusement bien compte

    Et je me doute que toute tentative de templatisation et de spécialisation du template risque fort d'être également refusée

    Le tout revient donc à savoir si le contrat peut être virtuel ou non
    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

  13. #53
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Le tout revient donc à savoir si le contrat peut être virtuel ou non
    Je préfère parler de contrat hérité plutôt que virtuel. Et cet héritage obéit à des règles précises, énoncées plus tôt dans ce thread .

    Encore une fois, la PPC n'est qu'une technique de conception et de programmation parmi d'autres, loin d'être la seule vérité. Mais elle part de certaines hypothèses, et, si tu les respectes de manière stricte, tout le reste en découle logiquement. Si tu changes les hypothèses, tu invalides le reste (et donc, tout le bénéfice qu'on en tire).

    J'ai vraiment l'impression que toi, tu veux changer à tout prix ces hypothèses

  14. #54
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Non, ma méthode de vérification est virtuelle et la fonction qui appelle la vérification utilise une référence.
    J'ai répondu trop court hier soir, donc je complète.
    Je reprend tes classes A et B :
    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
     
    class A
    {
        public:
            virtual bool pre(int i_) const{ return i_>=0;}
            virtual bool post() const{/* vérification après "de base" */}
            virtual void go() /* const */{/* comportement "de base" */}
            virtual void rollBack(){/* remise en état "de base" */}
    };
    class B: public A
    {
        public:
            virtual bool pre(int i_) const
            { return i_>5;        }
            virtual bool post() const
            {
                /* vérification des conditions plus restrictives, adaptées
                 * au comportement réel d'un B
                 */
            }
            virtual void go() /* const */
            {/* comportement "adapté" */}
            virtual void rollBack(){/* remise en état adaptée à B */}
    };
    Puis :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    void fooCertified(A & a,int i_)
    {
        if(a.pre(i_))
            a.go();
        if(!a.post())
            a.rollBack();
    }
    void fooUncertified(A& a)
    {
        a.go();
    }
    Utilisation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    int main()
    {
        A *pa = FabriqueMoiEstUnA(argv);
        #if defined(DDEBUG)
            fooCertified(pa,3);
        #else
            fooUnCertified(pa);
        #elseif
    }
    Ca marche en activant ou non les vérifications si pa représente un A, mais le comportement n'est pas le même si pa représente un B.

  15. #55
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    J'ai répondu trop court hier soir, donc je complète.
    Je reprend tes classes A et B :
    <sniped code>

    Ca marche en activant ou non les vérifications si pa représente un A, mais le comportement n'est pas le même si pa représente un B.
    Le comportement est adapté si pa est un B, et c'est normal dans un contexte de polymorphisme...

    Évidemment, on est bien d'accord sur le fait que go de A et go de B doivent poursuivre le meme objectif (si on remplaçait go par crier, parce que A est un animal, crier de B doit faire crier le B, et non le faire manger)

    Et je ne vois vraiment pas en quoi l'adaptation du comportement qui est soumis aux règles ET des règles serait refusée...

    N'oublions pas que l'héritage (publique) permet de faire passer un objet dérivé pour un objet "de base", tout en gardant en tête que c'est un objet de base "particulier" (qui peut être spécialisé ou généralisé)

    Lorsque je lit ce qui est mis sur le wiki pour la programmtion par contrat je me dis que:
    1. la précondition doit m'assurer que je peu appeler la fonction (que mon objet est en mesure de supporter le comportement que l'on envisage d'appeler)
    2. la postcondition doit me permettre de faire confiance à la fonction (que l'objet reste dans un état cohérent après avoir appelé le comportement)
    3. ca permet de décrire la sémantique du programme

    Mais il n'est mis nulle part qu'il n'est pas possible d'adapter les conditions si le comportement doit être adapté...

    Alors, il est peut être juste que je pars sur de mauvaise hypothèses, mais alors, dites moi quelles hypothèses je dois utiliser et en quoi les miennes sont mauvaises...
    Parce que, sur base des hypothèses que je vous présente, il faut quand même avouer que ma logique respecte l'ensemble des concepts et principes applicable en orienté objet.
    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

  16. #56
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Le comportement est adapté si pa est un B, et c'est normal dans un contexte de polymorphisme...
    Le comportement n'est pas le même lorsque pa est un B si la vérification est activée ou non : fooCertified verra la pré-condition non validée, donc STOP. Alors que pour fooUncertified il n'y a plus vérification de la pré-condition, donc GO alors qu'on voulait pas.
    Citation Envoyé par koala01 Voir le message
    Évidemment, on est bien d'accord sur le fait que go de A et go de B doivent poursuivre le meme objectif (si on remplaçait go par crier, parce que A est un animal, crier de B doit faire crier le B, et non le faire manger)
    Oui.
    Citation Envoyé par koala01 Voir le message
    N'oublions pas que l'héritage (publique) permet de faire passer un objet dérivé pour un objet "de base", tout en gardant en tête que c'est un objet de base "particulier" (qui peut être spécialisé ou généralisé)
    Sauf que LSP dit que partout où tu as un A, tu dois pouvoir y substituer un B. Or en restreignant les pré-conditions et en ne les vérifiant pas, tu ne peux plus substituer un B à un A sans avoir une erreur.
    Citation Envoyé par koala01 Voir le message
    Lorsque je lit ce qui est mis sur le wiki pour la programmtion par contrat je me dis que:
    1. la précondition doit m'assurer que je peu appeler la fonction (que mon objet est en mesure de supporter le comportement que l'on envisage d'appeler)
    2. la postcondition doit me permettre de faire confiance à la fonction (que l'objet reste dans un état cohérent après avoir appelé le comportement)
    3. ca permet de décrire la sémantique du programme

    Mais il n'est mis nulle part qu'il n'est pas possible d'adapter les conditions si le comportement doit être adapté...
    Oui mais le noeud c'est le LSP qui dit que tu dois pouvoir substituer un B à un A. Donc une fonction qui manipule un A doit pouvoir manipuler un B sans restriction.
    Citation Envoyé par koala01 Voir le message
    Alors, il est peut être juste que je pars sur de mauvaise hypothèses, mais alors, dites moi quelles hypothèses je dois utiliser et en quoi les miennes sont mauvaises...
    Parce que, sur base des hypothèses que je vous présente, il faut quand même avouer que ma logique respecte l'ensemble des concepts et principes applicable en orienté objet.
    Je me demande si la confusion ne vient pas du fait que B peut être un sous-type de A, c'est à dire avoir un espace de valeur 'inférieure'. Mais là, je recite white_tentacle :
    On dit souvent qu'un des "problèmes" de la programmation par contrat, c'est que B::f() inclut une précondition évidente : "l'appelant est un B", et que cette précondition est plus restrictive que celle de A::f() qui est "l'appelant est un A". C'est effectivement vrai, mais c'est un mauvais argument, dans le sens ou cette précondition est garantie par le compilateur, et ne fait pas réellement partie du contrat.

  17. #57
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Alors, il est peut être juste que je pars sur de mauvaise hypothèses, mais alors, dites moi quelles hypothèses je dois utiliser et en quoi les miennes sont mauvaises...
    Pars des hypothèses suivantes :

    - la précondition doit être vérifiable par l'appelant (ie, l'appelant doit être en mesure de vérifier qu'il respecte sa part du contrat)
    - le test du contrat dans l'appelé est totalement facultatif, il n'est là théoriquement qu'en phase de debug

    Je pense que si tu fournis une méthode virtuelle de vérification à appeler pour savoir si tu as le droit d'appeler la fonction, tu déformes complètement l'idée de départ. Parce que dans ce cas, ce que tu fais, c'est imposer à l'appelant la structure suivante :

    Et finalement, si tu le faisait dans ta classe, plutôt que de l'imposer à l'appelant, ça donnerait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    function safe_f() pre Nothing
    safe_f() n'a pas de préconditions, et du coup, tu n'as pas "restreint" les préconditions d'appel...

    Pour aller encore plus loin, suppose que j'ai les fonctions suivantes :

    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
    class A {
    int carre(int x) pre Nothing, post ret >= 0 { return x * x; }
    virtual int f(int x) pre x >= 0, post ret >= 0 { return (int) sqrt(x); }
    };
     
    class B {
    virtual int f(int x) pre x >= 2, post ret >=0 { return (int) sqrt(x-2); }
    }
     
    int main()
    {
       A* a1 = new A();
       A* a2 = new B();
       a1->f(a1->carre(1)); // super !
       a2->f(a2->carre(1)); // aïe !
    }
    Ce serait extrêmement vicieux. J'ai un A, j'appelle carre dessus, la postcondition sur le résultat me garantit qu'il est >= 0. La précondition sur A::f me dit que j'ai besoin que mon entier soit >= 0, ça tombe bien, c'est ce que carre me garantit !

    Si j'autorise B à restreindre les préconditions de A, mon programme va planter lamentablement, car je n'ai plus rien de tangible auquel me fier. J'ai perdu tout le bénéfice de mon contrat.

  18. #58
    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 : 49
    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
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Soit A::f(int i) qui requiert i > 0

    Soit B dérive de A. Tu peux avoir B::f(int i) qui requiert seulement i >= 0

    C'est parfaitement LSP-compatible. Si E(A) est l'ensemble des entrées valides de A, alors E(B) est un ensemble contenant au moins E(A). Inversement, S(B) est un sous-ensemble de S(A).

    Je ne connais pas de moyen de garantir ça en C++ (et il me semble avoir lu de James Kanze que ce n'est pas possible).
    Désolé si je n'ai pas lu l'ensemble du reste de la discussion dans les détails... Arrêtez-moi si ce que je dis a déjà été discuté. Pour coder ça, je mettrias en place:

    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
    class A
    {
    public:
      void f(int i)
      {
        assert(i>0);
        doF(i);
      }
    private:
      virtual void doF(int i) {...}
    };
     
    class B
    {
    public:
      void f(int i)
      {
        assert(i>=0);
        doF(i);
      }
    private:
      virtual void doF(int i) {...}
    };
    Ainsi, un utilisateur qui a en main un B, mais ne le connais que par l'intermédiaire d'un A* sera tenu à respecter les pré-conditions fixées par l'interface A. Un utilisateur qui tiens ce même objet par l'intermédiaire d'un B* peut lui utiliser l'interface plus souple fournie à ce niveau.
    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.

  19. #59
    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 611
    Points
    30 611
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Le comportement n'est pas le même lorsque pa est un B si la vérification est activée ou non : fooCertified verra la pré-condition non validée, donc STOP. Alors que pour fooUncertified il n'y a plus vérification de la pré-condition, donc GO alors qu'on voulait pas.
    Non, si tu adapte la pré condition de B de manière cohérent (qu'elle renvoie vrai quand la pré condition est valide) elle renvoie GO quand un A valide aurait renvoyé GO et STOP quand un A invalide aurait renvoyé STOP, le fait que la précondition ait été adaptée ne change pas le comportement de validation, donc le comportement général reste tout à fait identique (modulo les adaptations nécessaires à la prise en compte des spécifications de B)
    Sauf que LSP dit que partout où tu as un A, tu dois pouvoir y substituer un B. Or en restreignant les pré-conditions et en ne les vérifiant pas, tu ne peux plus substituer un B à un A sans avoir une erreur.

    Oui mais le noeud c'est le LSP qui dit que tu dois pouvoir substituer un B à un A. Donc une fonction qui manipule un A doit pouvoir manipuler un B sans restriction.
    A te lire, pour respecter le LSP nous devrions nous limiter à un héritage d'inclusion (où une classe dérivée ne peut que rajouter des comportement sans adapter ceux de la classe de base) ou à un héritage de réalisation (ou la classe dérivée ne fait que fournir des valeurs particulières à la classe de base)...

    Je suis désolé, mais je n'estimes pas être en désaccord avec LSP si j'ai un objet dérivé d'un objet de base, dont je peux sémantiquement dire qu'il est effectivement un objet de base (il ne faut pas que j'essaye de faire passer un chien pour un véhicule ), mais dont j'ai adapté un comportement (j'ai redéfini une méthode dans l'objet dérivé) et que ce comportement adapté poursuit le même objectif et fournit un résultat cohérent par rapport au comportement "de base" (et donc que l'on évite que le comportement de base consiste à crier alors que le comportement adapté consiste à manger, et que les deux comportements renvoient un GO si c'est valide et un STOP si c'est invalide)

    La substitution est parfaite: je peux substituer un objet dérivé à un objet de base sans que je ne fasse mentir en quoi que ce soit l'invocation des différents comportements que l'on attend de la part de l'objet de base.
    Je me demande si la confusion ne vient pas du fait que B peut être un sous-type de A, c'est à dire avoir un espace de valeur 'inférieure'. Mais là, je recite white_tentacle :
    On dit souvent qu'un des "problèmes" de la programmation par contrat, c'est que B::f() inclut une précondition évidente : "l'appelant est un B", et que cette précondition est plus restrictive que celle de A::f() qui est "l'appelant est un A". C'est effectivement vrai, mais c'est un mauvais argument, dans le sens ou cette précondition est garantie par le compilateur, et ne fait pas réellement partie du contrat.
    Mais pour moi, la condition n'est qu'un comportement, destiné à donner le GO ou le STOP...

    A partir de là, il n'y a aucune raison pour empêcher - toujours en gardant en tête que le GO est donné si c'est valide et le STOP si c'est invalide - une adaptation de la condition à la situation réelle de l'objet en cours de traitement, même si on le fait passer pour un objet de base
    Citation Envoyé par white_tentacle Voir le message
    Pars des hypothèses suivantes :

    - la précondition doit être vérifiable par l'appelant (ie, l'appelant doit être en mesure de vérifier qu'il respecte sa part du contrat)
    - le test du contrat dans l'appelé est totalement facultatif, il n'est là théoriquement qu'en phase de debug
    Hé bien, en quoi est-ce que je ne respecte pas ces hypothèses

    En faisant de la condition un comportement publique de l'objet traité, je m'assure qu'il sera utilisable de n'importe quel endroit où on trouve cet objet et donc où la condition est susceptible d'être testée

    Au pire, tu peux me reprocher d'avoir mal placé mes conditions de compilation...

    Plaçons les donc de telle manière à ce que les comportements vérifiant les pré et post conditions ne soient compilés qu'en mode débug
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class A
    {
        public:
            #if defined(DDEBUG)
            virtual void pre() const;
            virtual void post() const;
            #endif
    };
    car pre et post lancent des exceptions
    Je pense que si tu fournis une méthode virtuelle de vérification à appeler pour savoir si tu as le droit d'appeler la fonction, tu déformes complètement l'idée de départ. Parce que dans ce cas, ce que tu fais, c'est imposer à l'appelant la structure suivante :

    Mais, de toute manière, tu dois travailler avec des conditions (au minimum de compilation)... Utilisons donc un défine qui aille bien
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    #if defined(DDEBUG)
    #define PRE(X) X
    #define POST(X) X
    #else
    #define PRE(X)
    #define POST(X)
    #endif
    (j'ai peut etre fait quelque erreurs )
    qui seraient utilisés sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    A a;
    /* B hérite de A, et redéfini potentiellement les différentes fonctions */
    B b;
    PRE(a.pre())
    a.go();
    POST(a.post())
     
    PRE(b.pre())
    b.go();
    POST(b.post(),b)
    En utilisant des termes plus explicites que pre, go, post et rollback pour les différentes méthodes, nous atteignons le troisième but de la programmation par contrat qui est de donner des indications sur la sémantique du programme

    Du fait du lancement de l'exception, en mode debug, tu te trouve face à la remontée de cette exception jusqu'au plus haut, et donc avec la possibilité de la récupérer "en temps utiles".

    Au pire, tu te trouvera dans une situation où tu placera un try... catch te permettant de récupérer une exception qui ne sera jamais lancée (si on a quitté le mode débug)

    Même en partant de tes hypothèses (que j'admets tout à fait comme justifiées), les miennes ne sont pas en désaccord avec les tiennes
    Et finalement, si tu le faisait dans ta classe, plutôt que de l'imposer à l'appelant, ça donnerait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    function safe_f() pre Nothing
    safe_f() n'a pas de préconditions, et du coup, tu n'as pas "restreint" les préconditions d'appel...

    Pour aller encore plus loin, suppose que j'ai les fonctions suivantes :

    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
    class A {
    int carre(int x) pre Nothing, post ret >= 0 { return x * x; }
    virtual int f(int x) pre x >= 0, post ret >= 0 { return (int) sqrt(x); }
    };
     
    class B {
    virtual int f(int x) pre x >= 2, post ret >=0 { return (int) sqrt(x-2); }
    }
     
    int main()
    {
       A* a1 = new A();
       A* a2 = new B();
       a1->f(a1->carre(1)); // super !
       a2->f(a2->carre(1)); // aïe !
    }
    Pourquoi aie si tu a mis comme condition que x devait etre plus grand ou égal à 2 lorsque tu travaille sur un objet de type B, c'est que cette condition est justifiée et que le comportement risque de placer l'objet B dans un état incohérent

    Au contraire, je serais presque embêté de laisser passer un objet dérivé devenu incohérent parce que, du point de vue de l'objet de base, la cohérence est assurée
    Ce serait extrêmement vicieux. J'ai un A, j'appelle carre dessus, la postcondition sur le résultat me garantit qu'il est >= 0. La précondition sur A::f me dit que j'ai besoin que mon entier soit >= 0, ça tombe bien, c'est ce que carre me garantit !

    Si j'autorise B à restreindre les préconditions de A, mon programme va planter lamentablement, car je n'ai plus rien de tangible auquel me fier. J'ai perdu tout le bénéfice de mon contrat.
    Si, la cohérence du type réellement utilisé

    Je comprend ton point de vue, mais je trouve le mien bien plus logique:

    Dés le moment où tu peux substituer un objet à un autre, et que tu teste la cohérence de ton objet avant ou après une action, il me semble normal que ce soit le type réel qui détermine la cohérence de ce dernier (si du moins les règles de cohérences doivent être adaptées)

    Je sais que, bien que d'un point de vue purement géométrique, un carré est un rectangle "particulier", il ne faut pas permettre à carré d'hériter de rectangle parce que cela ne correspond à rien de parler de "longueur" et de "largeur" sur un carré.

    Par contre, un carré peut parfaitement hériter de losange (si on utilise comme donnée les quatre coins, et qu'il n'y a pas de données sur la taille des diagonales)

    En effet, les deux seules différences tangible entre un carré et un losange sont (l'une dépendant directement de l'autre) l'angle formé par les différents coté et la taille des diagonales.

    Les règles à appliquer pour t'assurer de la cohérence d'un carré sont plus strictes que celles à appliquer pour un losange (il faut soit s'assurer que les coté forment des angles droits, soit s'assurer que les diagonales sont égales... ce qui est peut être la solution la plus simple à mettre en oeuvre)

    Lors de la vérification du contrat d'un losange, tu va veiller à ce que ce soit bel et bien un losange: quatre cotés égaux, que ce soit avant ou après le comportement qui doit le modifier.

    Mais lors de la vérification du contrat d'un carré, le fait qu'il garde quatre cotés égaux n'est déjà pas trop mal (au moins, ca reste un losange, donc le type de base), mais y rajouter le fait qu'il garde des diagonales égales est encore mieux, parce que, justement, cela fait partie des conditions sans lesquelles... tu n'a pas un carré...

    Or, même si tu fais passer ton carré pour un losange, et que tu le manipule en tant que tel, ce qui importe, c'est que, lorsque tu voudra le récupérer en tant que carré, il soit resté un carré, et qu'il ne soit pas devenu losange...

    Il est donc logique, même si tu fais passer ton carré pour un losange, que les conditions testent le fait qu'il est (ou est resté)... un carré
    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

  20. #60
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Ce serait extrêmement vicieux. J'ai un A, j'appelle carre dessus, la postcondition sur le résultat me garantit qu'il est >= 0. La précondition sur A::f me dit que j'ai besoin que mon entier soit >= 0, ça tombe bien, c'est ce que carre me garantit !

    Si j'autorise B à restreindre les préconditions de A, mon programme va planter lamentablement, car je n'ai plus rien de tangible auquel me fier. J'ai
    Si, la cohérence du type réellement utilisé
    Mais je ne *connais pas* le type réellement utilisé. Le gros bénéfice de la PPC, c'est qu'elle me *garantit* que ce qui est valable pour un A, l'est pour tout B qui dérive de A.


    Je sais que, bien que d'un point de vue purement géométrique, un carré est un rectangle "particulier", il ne faut pas permettre à carré d'hériter de rectangle parce que cela ne correspond à rien de parler de "longueur" et de "largeur" sur un carré.

    Par contre, un carré peut parfaitement hériter de losange (si on utilise comme donnée les quatre coins, et qu'il n'y a pas de données sur la taille des diagonales)
    Non. Il y a le même problème entre losange et carré, qu'entre rectangle et carré. Tout dépend de ton interface, et du contrat de tes fonctions.

    Pourquoi aie si tu a mis comme condition que x devait etre plus grand ou égal à 2 lorsque tu travaille sur un objet de type B, c'est que cette condition est justifiée et que le comportement risque de placer l'objet B dans un état incohérent
    Lorsque j'ai écrit main, je me suis basé sur ce que je connaissais. Le contrat de A::f. Je n'ai pas connaissance du contrat des classes dérivées. Je ne peux pas, leur ensemble est virtuellement infini. En supposant que je n'ai aucune règle sur le contrat dans les classes dérivées, je ne sais plus rien. J'ai un contrat, mais il ne me sert à rien, car je ne peux plus me baser dessus.

    Ces règles sont là comme des garde-fous. Grâce à ces règles, je sais que je n'ai *pas le droit* d'écrire le B::f que j'ai écrit, et donc, que j'ai la *garantie* que ma fonction, qui fonctionne pour un A, fonctionnera pour tout B ou C qui dérive de A, *sans que je doive modifier ma fonction appelante*.

    Je crois qu'on a un problème de communication . J'ai l'impression que ce que tu défends, c'est que "on peut s'en sortir sans" et "c'est trop restrictif". Mon point de vue à moi, c'est que les garanties qu'on gagne, valent largement les restrictions qu'on s'impose.

Discussions similaires

  1. redéfinition de méthode
    Par greg08 dans le forum Langage
    Réponses: 8
    Dernier message: 28/10/2008, 15h59
  2. Réponses: 8
    Dernier message: 02/07/2008, 00h15
  3. héritage et redéfinition de méthodes
    Par Braillane dans le forum Langage
    Réponses: 4
    Dernier message: 07/01/2007, 19h47
  4. redéfinition de méthode et invocation
    Par magedo dans le forum Langage
    Réponses: 2
    Dernier message: 05/01/2007, 20h00
  5. [JPanel][LayoutManager] redéfinition de méthodes
    Par _KB_ dans le forum AWT/Swing
    Réponses: 4
    Dernier message: 06/06/2006, 15h22

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