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 :

Le changement de visibilité d'une fonction virtuelle est-il un viol du LSP ?


Sujet :

Langage C++

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Sisi, je peux toujours et je crois qu'on est pas d'accord sur la signification du verbe pouvoir .
    Pouvoir: avoir l'autorisation de... à ne pas confondre avec être capable de...

    Le fait que tu soit capable d'invoquer un comportement suite à une décision inappropriée de conception ne signifie absolument pas qu'il soit opportun de te donner la permission de l'invoquer.

    La preuve avec un code tout simple:
    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
    class Base
    {
        public:
            virtual void foo()
            {std::cout<<"la fonction peut etre appelee de l'exterieur"
                      <<std::endl;}
    };
    class Derivee : public Base
    {
        protected :
            virtual void foo()
            {std::cout<<"la fonction ne peut servir qu'a usage interne"
                       <<std::endl;}
    };
    void bar(Base & b)
    {
        b.foo();
    }
    int main()
    {
        Base a;
        Derivee b;
        bar(a);
        bar(b);
    }
    donne, à l'exécution
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    la fonction peut etre appelee de l'exterieur
    la fonction ne peut servir qu'a usage interne
    Comment pourrais tu estimer que le programme fonctionne correctement, alors que la même fonction appelée exactement dans les mêmes circonstances te dit que... la fonction est limitée à usage interne
    Pourtant, ce choix aboutit à la même conséquence que le changement de visibilité : la fonction f est appelable au travers de l'interface de base, mais plus sur le type réel de l'objet (pour une raison différente, mais la conséquence est la même).
    Mais les causes sont différentes, le raisonnement est, lui aussi, tout à fait différent, et, surtout, les solutions potentielles sont tout à fait différentes...

    D'un coté, tu décide d'accepter un héritage qui n'a pas lieu d'être, parce que tu ne peux décemment pas estimer, en vertu de LSP qu'il y a une des partie de l'interface publique qui est valide pour la classe de base et invalide pour la classe dérivée.

    Comme j'ai déjà listé les différentes possibilités que tu as pour résoudre ce problème, je te reporte simplement à mes messages précédents.

    D'un autre, tu as simplement manqué de recul par rapport à l'ensemble de ta conception et il "suffit" de renommer la fonction dans une seule classe de base (et éventuellement dans les classes dérivées qui la redéfinissent)
    Pourquoi ce code devrait-il fonctionner en vertu du LSP ? Où est la substitution ?
    Parce que toute fonction qui peut être appelée depuis un objet du type de la classe de base doit pouvoir être appelée depuis une instance du type de la classe dérivée.

    Il n'y a pas substitution, mais le fait qu'une fonction puisse être appelée est bel et bien une propriété de la classe de base.
    Nulle part dans ce code on a substitué un B à un A. On a fait quelque chose sur un A, puis on essaie de faire quelque chose d'invalide avec un B.
    Si ce n'est que B hérite de A, et que donc, si l'appel d'une fonction est valide pour A, il doit aussi... être valide pour B, quitte à ce que le comportement observé soit adapté aux besoins de B.
    Mais nulle part il y a substitution, et certainement pas au niveau des objets.
    On met énormément l'accent sur la substituabilité avec LSP, mais ce n'est pas le seul point sur lequel il est applicable.

    Nous ne pouvons, en effet, envisager de substituer des objets que si LSP est validé, mais, à coté de cela, la validité de la relation EST-UN elle-même dépend du respect de LSP.

    Et donc, au delà de la subsituabilité entre un objet du type dérivé et un objet du type de base, c'est carrément tout ce que permet le type de base qui doit être permis par le type dérivé.
    Enlève moi tous ces types que je ne saurais voir, je dis depuis le début que le LSP s'applique aux instances. Je reformule, donc :
    Comme je te le répète depuis plusieurs interventions, tu ne peux pas dissocier une instance ou un objet du type qu'il (elle) représente.

    Quand je crée une golf 4 TDI, je crée une instance, mais pas une instance quelconque: une instance de voiture (et non une instance d'éléphant, par exemple).

    Le type regroupe (au niveau des fonctions membres) les comportements qui sont acceptables et les conditions dans lesquelles un objet (ou une instance) acceptera que l'on y fasse appel.

    A l'inverse, une instance ou un objet représente "physiquement" (ou du moins en mémoire) "quelque chose" (pour ne pas réutiliser le terme objet) qui accepte que l'on invoque un certains nombre de comportements, déterminés par le type, aux conditions déterminées par le type.
    Et là, fondamentalement, je ne vois plus où est le problème à restreindre la visibilité .
    j'ai répondu un peu plus haut, avec mon exemple de code
    Eiffel offre des mécanismes de ce type, au moyen de contrats (même si eiffel offre d'autres mécanismes qui ne respectent pas le LSP ). Mais je ne parle pas d'écrire du code qui ne vérifie pas le LSP (ce qui est très facile), je parle spécifiquement d'écrire du code qui aurait un comportement incohérent, en utilisant la fonctionnalité dont on est en désaccord pour savoir si elle respecte le LSP, et que l'incohérence soit due à une substitution d'objet. Ce qui me semble impossible, mais j'ai pu louper quelque chose.
    Tu as, effectivement loupé quelque chose...

    Cf mon code de début d'intervention

    De plus, je le répète encore une fois, LSP et un principe de conception, tout comme peut l'être celui de préciser l'accessibilité d'un membre ou d'une fonction membre.

    Si tu veux t'assurer que l'ensemble de ton programme fonctionne de manière cohérente, tu te dois de respecter les différents principes, règles et lois de la conception orientée objet.

    Si tu prend le risque de ne pas les respecter, tu cours celui d'avoir des comportements dont rien dans le processus de compilation (quel que soit le langage) ne te permet de te rendre compte que "tu joues avec le feu", et qui produiront des résultats au minimum aberrants, au pire, carrément catastrophiques.
    Et celles que le langage accepte. Ce qui est loin d'être négligeable dans certains langages.
    Mais les règles que le langage définit par lui-même ne traitent en gros que de la syntaxe à utiliser et de terme que le compilateur est capable de comprendre "par lui-même", plus quelques règles pour savoir quand il faut faire une copie ou non de l'objet manipulé, ou des règles de destruction implicite
    C'est tout le fond de notre désaccord. Pour moi, ce n'est pas nécessairement incohérent, et c'est rau contraire endu nécessaire par... le LSP .
    Reprenons, si tu veux bien...

    Tu es d'accord que, si tu écrit une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class Derivee
    {
        protected:
            virtual void foo(){std::cout<<"fonction a usage interne"
                                       <<std::endl;}
    };
    c'est parce que tu as déterminé, au moment de la conception, que foo te serait utile pour les objets de type Derivee, quelle pourrait être redéfinie pour les types qui héritent de Derivee, mais qu'il n'est pas opportun de laisser l'utilisateur de ta classe (celui qui créera une instance de Derivee) appeler cette fonction n'importe quand.

    Jusque là, je présumes que tu sera d'accord avec le raisonnement, non

    A coté de cela, si tu as une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class Base
    {
        public:
            virtual void foo(){std::cout<<"fonction accessible de partout"
                                        <<std::endl;}
    };
    C'est parce que tu as déterminé, toujours au moment de la conception, que foo serait utile pour les objets du type Base, qu'elle pourrait être redéfinie pour les types qui héritent de Base, et que l'on peut laisser l'utilisateur de ta classe (celui qui créera une instance de Base) invoquer cette fonction "à n'importe quel moment, depuis n'importe où", s'il le juge opportun.

    Jusqu'ici, nous sommes, à mon avis, toujours tout à fait d'accord

    Là où les choses se gâtent, c'est si l'on décide de faire en sorte que Derivee hérite de Base.

    En effet, l'utilisateur (ou toi même) pourrait(s) décider d'écrire une fonction proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void bar(Base & b)
    {
        b.foo();
    }
    pour les objets du type Base, il n'y a aucun problème : foo est déclarée accessible depuis l'extérieur, et on peut donc parfaitement envisager d'écrire un tel code.

    Par contre, pour les objets du type Derivee, pour lesquels tu as explicitement dit à l'utilisateur de ne pas utiliser foo, auquel tu as clairement stipulé "à usage interne uniquement", il en va tout autrement.

    En effet, s'il décide d'écrire la fonction principale sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main()
    {
        Derivee d;
        // d.foo() // refusé à la compilation : foo() est protected dans ce contexte
        bar(d);
        /* utilisation ultérieure de d; */
        return 0;
    }
    Le fait qu'il ne puisse pas invoquer foo depuis l'instance d de Derivee est normal et c'est le comportement que l'on attend d'un objet de type Derivee: foo est à usage interne uniquement.

    L'incohérence se situe au niveau de l'appel de bar en lui passant d en paramètre.

    Je te rappelle que d (qui prend le nom de b dans bar) est et ..."redevient" (même si ce terme est particulièrement mal choisi)... un objet de type Derivee une fois que l'on a quitté la portée de bar.

    Comment pourrait on justifier que le simple fait d'appeler une fonction qui n'est même pas déclarée comme une amie de Derivee permette d'appeler un comportement réputé comme étant à usage interne pour Derivee

    D'un coté, tu dis à l'utilisateur qu'il ne doit pas essayer d'invoquer foo sur un objet de type Derivee, parce qu'elle intervient dans la "popote interne" de l'objet en question, mais, de l'autre, tu le félicite d'avoir été "assez bête pour perdre le type réel de l'objet" en créant une fonction qui, elle, pourra accéder à la fonction sans aucune restriction uniquement parce que tu as fais un choix de conception que tu n'aurais jamais accepté si tu avais appliqué les principe de conception au bon moment.

    De plus, je tiens quand même à préciser que le prototype de bar "ment" bien souvent à son utilisateur, dans le sens où elle accepte "n'importe quel type susceptible de se faire passer pour Base", et que donc, à partir du moment où l'héritage est décidé, elle acceptera sans distinction n'importe quel (instance du ) type dérivé.

    Mais, encore une fois, comme bar(d) aura pour conséquence l'appel d'un comportement que nous n'aurions jamais pu invoquer si l'on avait considéré d pour ce qu'il est (à savoir... un objet de type Derivee), nous n'aurions jamais du donner la possibilité à bar d'accepter qu'on lui transmette un objet de type Derivee comme paramètre.
    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
    gl
    gl est déconnecté
    Rédacteur

    Homme Profil pro
    Inscrit en
    Juin 2002
    Messages
    2 165
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Juin 2002
    Messages : 2 165
    Points : 4 637
    Points
    4 637
    Par défaut
    J'ai l'impression que la divergence de point de vue vient essentiellement que vous ne parlaient de la même substituabilité :

    • De son côté, il me semble que white_tentacle envisage la substitualité dans le cadre de l'usage polymorphique uniquement (on fixe le type statique à A et on fait varier le type dynamique de A à B).
    • Alors que koala01 l'envisage dans un cadre plus large qui englobe le cas précédent mais couvre également le cas ou l'on fait varier le type statique et le type dynamique.


    A partir de là, il est assez normal que la conclusion soit différente.

    Personnellement, j'aurais plutôt tendante à considérer LSP dans le second cas, même si je comprends tout à fait le point de vue de white_tentacle.

  3. #43
    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 gl Voir le message
    J'ai l'impression que la divergence de point de vue vient essentiellement que vous ne parlaient de la même substituabilité :

    • De son côté, il me semble que white_tentacle envisage la substitualité dans le cadre de l'usage polymorphique uniquement (on fixe le type statique à A et on fait varier le type dynamique de A à B).
    • Alors que koala01 l'envisage dans un cadre plus large qui englobe le cas précédent mais couvre également le cas ou l'on fait varier le type statique et le type dynamique.


    A partir de là, il est assez normal que la conclusion soit différente.
    C'est en mieux ce que j'ai essayé de dire dans un message précédent. Tout dépend de ce que l'on entend par substituable.
    Citation Envoyé par gl Voir le message
    Personnellement, j'aurais plutôt tendante à considérer LSP dans le second cas, même si je comprends tout à fait le point de vue de white_tentacle.
    En revanche, je suis plus tenté par l'approche de white_tentacle qui est plus cohérente avec l'héritage et donc un polymorphisme d'inclusion.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Que ceux qui ne sont pas de mon avis me rassurent...

    Ils ne trouvent quand même pas normal qu'avec un programme 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
    class Base
    {
        public:
            virtual void foo()
            {std::cout<<"la fonction peut etre appelee de l'exterieur"
                      <<std::endl;}
    };
    class Derivee : public Base
    {
        protected :
            virtual void foo()
            {std::cout<<"la fonction ne peut servir qu'a usage interne"
                       <<std::endl;}
    };
    void bar(Base & b)
    {
        b.foo();
    }
    int main()
    {
        Base a;
        Derivee b;
        bar(a);
        bar(b);
    }
    on obtienne à l'exécution
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    la fonction peut etre appelee de l'exterieur
    la fonction ne peut servir qu'a usage interne
    La première ligne est tout à fait correcte, mais la deuxième...

    Cela ne vous chipote absolument pas de constater qu'un fonction vous dise qu'elle est à usage interne, alors que vous l'avez appelée depuis une fonction externe
    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

  5. #45
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par gl Voir le message
    J'ai l'impression que la divergence de point de vue vient essentiellement que vous ne parlaient de la même substituabilité :

    • De son côté, il me semble que white_tentacle envisage la substitualité dans le cadre de l'usage polymorphique uniquement (on fixe le type statique à A et on fait varier le type dynamique de A à B).
    • Alors que koala01 l'envisage dans un cadre plus large qui englobe le cas précédent mais couvre également le cas ou l'on fait varier le type statique et le type dynamique.


    A partir de là, il est assez normal que la conclusion soit différente.

    Personnellement, j'aurais plutôt tendante à considérer LSP dans le second cas, même si je comprends tout à fait le point de vue de white_tentacle.
    Oui, c'est ca, à part que dans l'énoncé du LSP il n'est pas question de typage statique et dynamique, donc il n'y a pas trop de raison de le considérer lors de l'analyse, on se retrouve donc focément dans le second cas et le LSP est violé. Que le premier cas marche est juste du au langage, et le dernier exemple de koala montre bien que c'est assez illogique.

    En revanche, je suis plus tenté par l'approche de white_tentacle qui est plus cohérente avec l'héritage et donc un polymorphisme d'inclusion.
    C'est vraie que le premier cas est le cas ou le LSP est vraiment utile (si l'on ne profite pas du polymorphisme et des méthodes virtuelle vérifier le LSP est un peu inutile), mais encore une fois, le dernier exemple de koala montre bien que même dans ce cas là le comportement peut-etre assez incohérent.

  6. #46
    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
    Citation Envoyé par koala01 Voir le message
    Cela ne vous chipote absolument pas de constater qu'un fonction vous dise qu'elle est à usage interne, alors que vous l'avez appelée depuis une fonction externe
    Je pourrais dire que j'ai appelé une fonction à usage externe, qui a redirigé l'appel vers une fonction à usage interne, ce qui en soit n'est pas choquant.

    Par contre, comme l'a dit gl, on est d'accord sur beaucoup de choses, à savoir que ce n'est pas une pratique que je conseillerais, qu'il y a probablement un défaut de conception sauf cas particuliers, etc.

    Y a juste sur les définitions du LSP, des mots substituabilité, instance, type, et du rôle de chacun dans l'affaire que là ça coince .

  7. #47
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je pourrais dire que j'ai appelé une fonction à usage externe, qui a redirigé l'appel vers une fonction à usage interne, ce qui en soit n'est pas choquant.
    D'accord, donc tu as une propriété vraie pour un objet mère (la méthode est à usage externe, ie le message est recevable), et fausse pour un objet fille (la méthode est à usage interne, ie le message n'est pas recevable). Ca prouve que le LSP est violé.

    Citation Envoyé par white_tentacle Voir le message
    Y a juste sur les définitions du LSP, des mots substituabilité, instance, type, et du rôle de chacun dans l'affaire que là ça coince .
    LSP : Soit q(x) une propriété prouvable à propos des objets x de type T. Alors q(y) doit être vrai pour les objets y de type S lorsque S est un sous-type de T. (trad. Emmanuel Deloget)

    Dans cette définiton le type est le type réel, donc en C++ le type dynamique. Je crois que tout le monde sais ce qu'est une instance (=objet) d'une classe. Pour la substituabilité, je ne vois pas trop ce qu'elle vient faire dans la vérification du LSP ! (le LSP assure juste que dans le code et pour peu que le langage permet l'application du LSP, tout objet de type fille est substituable à un objet de type mère, mais la réciproque est fausse, ce n'est pas parce que tout le code que tu écris permet ces substitutions que le LSP est respecté).

    Par contre, comme l'a dit gl, on est d'accord sur beaucoup de choses, à savoir que ce n'est pas une pratique que je conseillerais, qu'il y a probablement un défaut de conception sauf cas particuliers, etc.
    Pour ca je pense aussi que tout le monde est d'accord

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je pourrais dire que j'ai appelé une fonction à usage externe, qui a redirigé l'appel vers une fonction à usage interne, ce qui en soit n'est pas choquant.
    Ce qui serait choquant, c'est que la fonction (normalement à usage interne) puisse modifier l'objet sur lequel elle agit...

    Il ne faut pas oublier que, si l'on décide de déclarer une fonction dans une accessibilité retstreinte (AKA protected ou private), c'est que l'on a de bonnes raisons de le faire, a fortiori si la fonction modifie l'objet.

    Que l'on puisse malgré tout invoquer cette fonction depuis l'extérieur, alors que l'on a clairement décidé qu'il fallait en restreindre l'usage est une aberration pure et simple et ouvre la porte à une série d'usages inconsidérés.

    Encore une fois, on récompense le fait que l'on ait été assez bête pour perdre le type réel lors du passage d'argument!
    Par contre, comme l'a dit gl, on est d'accord sur beaucoup de choses, à savoir que ce n'est pas une pratique que je conseillerais, qu'il y a probablement un défaut de conception sauf cas particuliers, etc.
    Je ne vois aucun cas particulier dans lequel il n'y aurait pas un défaut de conception.

    Et pourtant, dieu m'est témoin que je suis sans doute l'un de ceux qui acceptent de les envisager les plus facilement

    Si la décision de déclarer une fonction (qu'elle soit virtuelle ou non) dans une visibilité restreinte a été prise, il n'y a strictement aucune raison que l'on décide que, parce que l'on travaille sur le type parent, on puisse y avoir accès.

    Encore une fois, je serais plus facile à convaincre, pour peu que vous m'apportiez des arguments cohérents, si vous envisagiez de faire le contraire (de faire passer une fonction privée ou protégée dans la visibilité publique)
    Y a juste sur les définitions du LSP, des mots substituabilité, instance, type, et du rôle de chacun dans l'affaire que là ça coince .
    LSP doit être considéré comme prérequis à toute décision d'appliquer un héritage public, autrement, il n'a aucun sens, et n'apporte plus la moindre sécurité.

    Si Madame Liskov a énoncé le principe, ce n'est ni pour entretenir une polémique telle que celle qui survient sur cette discussion, ni pour nous embêter, mais bel et bien pour nous fournir les outils qui nous permettront de travailler de manière cohérente et sécurisante.

    Si on décide de transiger avec ce principe, c'est carrément l'ensemble de la conception qui devient bancal et on finit par se retrouver avec une espèce de colosse au pieds d'argile qui ne demande qu'à s'écrouler dés que l'utilisateur a le malheur d'appeler une fonction qu'il n'aurait jamais du pouvoir invoquer si la conception avait appliqué le principe "à la lettre".

    Traitez moi de puriste, de théoricien ou de farfelu si vous le voulez, mais, en tant que responsable de projet, ce sera quelque chose que je refuserai systématiquement.

    Maintenant, si cela vous amuse de vous tirer une balle dans le pied... J'espère que vous serez prêts à en subir les conséquences qui ne manqueront pas de survenir
    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

  9. #49
    gl
    gl est déconnecté
    Rédacteur

    Homme Profil pro
    Inscrit en
    Juin 2002
    Messages
    2 165
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Juin 2002
    Messages : 2 165
    Points : 4 637
    Points
    4 637
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    En revanche, je suis plus tenté par l'approche de white_tentacle qui est plus cohérente avec l'héritage et donc un polymorphisme d'inclusion.
    De mon point de vu le premier cas n'est qu'un "simple" cas particulier du second cas général. C'est pour cela que j'aurais tendance à prendre la seconde définition.

    Mais, comme tu le fais remarquer, en pratique, la substitution intervient généralement dans le cadre d'un usage polymorphique de tes objets. C'est la raison pour laquelle je comprends que l'on puisse se contenter du premier cas pour juger du respect du LSP même si je ne partage pas vraiment ce point de vue.

  10. #50
    gl
    gl est déconnecté
    Rédacteur

    Homme Profil pro
    Inscrit en
    Juin 2002
    Messages
    2 165
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Juin 2002
    Messages : 2 165
    Points : 4 637
    Points
    4 637
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Je ne vois aucun cas particulier dans lequel il n'y aurait pas un défaut de conception.
    C'est un peu le propre des cas particuliers, tant qu'on ne les a pas rencontré on ne les imagine pas.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par gl Voir le message
    C'est un peu le propre des cas particuliers, tant qu'on ne les a pas rencontré on ne les imagine pas.
    De fait...

    Et tu sais que je suis globalement favorable à l'argument du cas particulier.

    Mais, à partir du moment où l'on décide de restreindre l'accès à une fonction membre, c'est qu'il y a des raisons de le faire et, comme on dispose de suffisamment de moyens pour "adoucir" ces restrictions, la seule idée de cautionner une erreur de conception afin d'obtenir un résultat similaire me choque énormément.
    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
    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
    Je veux juste revenir sur ce qui a été dit à propos de "la phase de conception est indépendante". En fait, c'est faux, la phase de conception est toujours fonction du langage cible (ou alors, d'une restriction imposée par le formalisme).

    Si je sais que mon programme va tourner sur une machine virtuelle java 1.4, alors je sais que je n'aurais pas de génériques, et donc, je vais les écarter dès la conception.

    Cette petite remarque pour amener la chose suivante. Il est généralement admis que les paramètres covariants enfreignent le LSP, car ils restreignent les préconditions de ma fonction. Pourtant, on le fait tous les jours avec le paramètre implicite this et ça ne choque plus personne depuis longtemps. Je vais aller un peu plus loin, et dire qu'en réalité, ça ne pose aucun problème pour tout paramètre sur lequel on base le dispatch. Dans un langage en multiple dispatch, c'est parfaitement sain. On aurait donc quelque chose qui violerait le LSP dans un cas, mais pas dans l'autre, en fonction du langage utilisé (et des mécanismes de celui-ci) ? Pas très compatible avec l'idée que c'est un principe de conception pure...

    Ceci pour dire que le LSP doit s'envisager dans le contexte d'utilisation. C'est un principe de conception général, mais sa matérialisation va donner des différences en fonction du contexte (this covariant est une restriction de précondition, chose normalement interdite !).

    Pour revenir à nos moutons et à notre changement de visibilité, si la visibilité était vérifié au run-time (ce qui est un choix du langage, du contexte, donc), l'appel de f sur un B à travers un A résulterait en un accès denied, alors qu'il fonctionnerait sur un A --> pas très LSP-compatible tout ça. Ce que le LSP nous suggère, c'est que dans un langage où la visibilité est vérifiée au runtime, ce n'est vraiment pas une bonne idée d'autoriser de restreindre la visibilité d'une fonction dans une classe dérivée car ça garantit une rupture de LSP (à mon sens, un langage ne peut pas garantir que tu respectes le LSP, mais il ne doit pas t'offrir des fonctionnalités qui garantissent que tu ne le respectes pas).

    Par contre, la visibilité étant vérifiée à la compilation et pas à l'exécution, la réponse est moins nette. Mon propos est depuis le début de dire que l'appel de f sur un B à travers A continuant de fonctionner et de donner le résultat attendu, le LSP est tout à fait respectable (modulo l'implémentation de B::f) dans ce contexte. Celui de Koala est de dire que c'est une ignominie de conception (et j'aurais tendance à être d'accord), qu'il justifie avec le LSP (c'est là où je ne suis pas d'accord).

    Pour en rajouter une couche, ça casse la programmation générique, où l'on ne peut pas substituer le type B au type A. Mais c'est loin d'être la seule opération qui empêche la substitution, du point de vue des générique à la C++. Les types sont des objets bien particuliers, et je ne suis pas sûr que sous prétexte que B dérive de A, et que donc un B EST-UN A, il faut que TypeB EST-UN TypeA (ce n'est pas le cas en C++, je ne sais pas pour les autres langages permettant de manipuler les types statiques).

  13. #53
    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 koala01 Voir le message
    Que ceux qui ne sont pas de mon avis me rassurent...

    Ils ne trouvent quand même pas normal qu'avec un programme 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
    class Base
    {
        public:
            virtual void foo()
            {std::cout<<"la fonction peut etre appelee de l'exterieur"
                      <<std::endl;}
    };
    class Derivee : public Base
    {
        protected :
            virtual void foo()
            {std::cout<<"la fonction ne peut servir qu'a usage interne"
                       <<std::endl;}
    };
    void bar(Base & b)
    {
        b.foo();
    }
    int main()
    {
        Base a;
        Derivee b;
        bar(a);
        bar(b);
    }
    on obtienne à l'exécution
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    la fonction peut etre appelee de l'exterieur
    la fonction ne peut servir qu'a usage interne
    La première ligne est tout à fait correcte, mais la deuxième...
    Oui, je trouve ça anormal. Mais non, je ne pense pas qu'il y a viol du LSP lié au fait de passer la fonction en protected dans la classe dérivée. Je pense que le viol du LSP a lieu quand dans l'implémentation de B::foo, le développeur a écrit une ânerie en disant que la fonction ne peut servir qu'à usage interne.

    Citation Envoyé par koala01 Voir le message
    Il ne faut pas oublier que, si l'on décide de déclarer une fonction dans une accessibilité retstreinte (AKA protected ou private), c'est que l'on a de bonnes raisons de le faire, a fortiori si la fonction modifie l'objet.
    Sauf que pour moi, tu n'as pas déclaré une fonction dans une portée restreinte dans l'absolu. Tu as juste dit que tu interdisais de l'appeler depuis un objet dont le type statique était B.
    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.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je veux juste revenir sur ce qui a été dit à propos de "la phase de conception est indépendante". En fait, c'est faux, la phase de conception est toujours fonction du langage cible (ou alors, d'une restriction imposée par le formalisme).

    Si je sais que mon programme va tourner sur une machine virtuelle java 1.4, alors je sais que je n'aurais pas de génériques, et donc, je vais les écarter dès la conception.
    Je mettrais une nuance:

    Tout en étant indépendant du langage que tu vas utiliser au niveau de la conception, tu prend les restrictions imposées par celui-ci en compte afin d'éviter d'avoir à revenir sur ta conception par la suite... Ce qui est normal, parce que je présumes que tu es au moins aussi fainéant que moi, dans le sens où tu préfères faire correctement les choses dés le départ

    L'impossibilité d'avoir un héritage multiple ou celle de disposer des génériques ne sont que des restrictions imposées par le langage

    Pour revenir à nos moutons et à notre changement de visibilité, si la visibilité était vérifié au run-time (ce qui est un choix du langage, du contexte, donc), l'appel de f sur un B à travers un A résulterait en un accès denied, alors qu'il fonctionnerait sur un A --> pas très LSP-compatible tout ça. Ce que le LSP nous suggère, c'est que dans un langage où la visibilité est vérifiée au runtime, ce n'est vraiment pas une bonne idée d'autoriser de restreindre la visibilité d'une fonction dans une classe dérivée car ça garantit une rupture de LSP (à mon sens, un langage ne peut pas garantir que tu respectes le LSP, mais il ne doit pas t'offrir des fonctionnalités qui garantissent que tu ne le respectes pas).

    Par contre, la visibilité étant vérifiée à la compilation et pas à l'exécution, la réponse est moins nette. Mon propos est depuis le début de dire que l'appel de f sur un B à travers A continuant de fonctionner et de donner le résultat attendu, le LSP est tout à fait respectable (modulo l'implémentation de B::f) dans ce contexte. Celui de Koala est de dire que c'est une ignominie de conception (et j'aurais tendance à être d'accord), qu'il justifie avec le LSP (c'est là où je ne suis pas d'accord).
    Le fait est que tu pars du principe que tu ne fais pas une erreur en décidant que ta classe dérivée hérite de ta classe de base...

    Or, mon approche est que tu as déjà fait une erreur en prenant cette décision, parce qu'une des propriétés vérifiée pour la classe de base n'est pas vérifiée pour la classe dérivée.

    Il ne faut pas t'étonner si, ayant pris une mauvaise décision à la base, le langage ne peut que... respecter les règles que tu as imposées, parce qu'il n'a aucun moyen de juger de l'opportunité d'un héritage.

    Pour en rajouter une couche, ça casse la programmation générique, où l'on ne peut pas substituer le type B au type A. Mais c'est loin d'être la seule opération qui empêche la substitution, du point de vue des générique à la C++. Les types sont des objets bien particuliers, et je ne suis pas sûr que sous prétexte que B dérive de A, et que donc un B EST-UN A, il faut que TypeB EST-UN TypeA (ce n'est pas le cas en C++, je ne sais pas pour les autres langages permettant de manipuler les types statiques).
    La programmation générique est un cas tout à fait à part...

    La meilleure preuve en est que l'on peut, justement, y faire appel pour s'assurer que plusieurs types présentent une interface commune sans présenter de base commune
    Citation Envoyé par JolyLoic Voir le message
    Oui, je trouve ça anormal. Mais non, je ne pense pas qu'il y a viol du LSP lié au fait de passer la fonction en protected dans la classe dérivée. Je pense que le viol du LSP a lieu quand dans l'implémentation de B::foo, le développeur a écrit une ânerie en disant que la fonction ne peut servir qu'à usage interne.
    Pas du tout: pour la classe dérivée, foo est bel et bien à usage interne uniquement...
    Sauf que pour moi, tu n'as pas déclaré une fonction dans une portée restreinte dans l'absolu. Tu as juste dit que tu interdisais de l'appeler depuis un objet dont le type statique était B.
    Si ce n'est que le type statique de mon objet deviendra le type dynamique de toute référence ou pointeur dans une fonction acceptant la classe de base.

    Encore une fois, tu sembles estimer qu'il n'y a pas d'erreur dans le fait de décider de l'héritage, alors que l'exécution même te montre que tu as effectivement fait une erreur à la base en décidant que Derivee hérite de Base.

    Que foo soit virtuelle et protégée dans Derivee pourrait très bien se justifier si l'on venait à prévoir qu'une troisième classe puisse hériter (en respectant LSP!!!) de Derivee.

    Mais que l'on accepte que Derivee hérite de Base alors qu'une des propriétés vérifiées de Base (le fait de pouvoir appeler foo depuis n'importe où) ne le soit pas pour Derivee, là, je dis NON: Problème de conception.

    Pourquoi diable ne pas accepter l'idée que le seul fait d'accepter que Derivee hérite de Base est une erreur de conception LSP le met pourtant clairement en évidence: une des propriétés vérifiées pour la classe de base n'est pas vérifiée pour la classe dérivée, et donc, on ne peut pas envisager l'héritage public, point-barre.

    [EDIT]Si l'on tient à ce que Base et Derivee aient une base commune, il faudra rajouter une classe n'exposant absolument pas la fonction qui est protégée dans l'une et publique dans l'autre, et faire hériter Base et Derivee de cette classe supplémentaire sans faire hériter Derviee de Base
    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. #55
    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
    Citation Envoyé par koala01 Voir le message
    Or, mon approche est que tu as déjà fait une erreur en prenant cette décision, parce qu'une des propriétés vérifiée pour la classe de base n'est pas vérifiée pour la classe dérivée.
    Tu as squeezé dans ta réponse toute mon explication sur le fait que single dispatch fait la même chose, il restreint une propriété de la classe de base (à savoir, f prend un A comme paramètre this en f prend un B comme paramètre this), ainsi que les conséquences du multiple-dispatch sur le LSP et les paramètres covariants. Tu continues de t'abstraire du contexte pour affirmer qu'une propriété n'est pas vérifiée, alors que dans le contexte, elle l'est.

    Encore une fois, tu sembles estimer qu'il n'y a pas d'erreur dans le fait de décider de l'héritage, alors que l'exécution même te montre que tu as effectivement fait une erreur à la base en décidant que Derivee hérite de Base.
    Il n'y pas besoin de violer le LSP pour faire des erreurs de conception .

    Pourquoi diable ne pas accepter l'idée que le seul fait d'accepter que Derivee hérite de Base est une erreur de conception
    Personne n'a défendu que c'était une pratique à recommander, plutôt le contraire.

    LSP le met pourtant clairement en évidence: une des propriétés vérifiées pour la classe de base n'est pas vérifiée pour la classe dérivée, et donc, on ne peut pas envisager l'héritage public, point-barre.
    A t'écouter, aucun héritage n'est possible, car chaque fois qu'il y a héritage et méthode virtuelle, il y a restriction des préconditions sur this, et donc on viole le LSP Je pousse ton raisonnement à l'extrême, mais c'est pour essayer de t'amener à changer ta vision sur le LSP, qui pour moi doit toujours s'envisager par rapport au contexte réel (où il n'y a aucun problème à restreindre la précondition sur this, puisque les mécanismes du langage nous offrent les garanties nécessaires --> influence du contexte sur le LSP).

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Il n'y pas besoin de violer le LSP pour faire des erreurs de conception .
    Tout à fait...

    Mais, ici, c'est bien le viol de LSP qui est à l'origine de cette erreur de conception
    Personne n'a défendu que c'était une pratique à recommander, plutôt le contraire.
    Encore heureux
    A t'écouter, aucun héritage n'est possible, car chaque fois qu'il y a héritage et méthode virtuelle, il y a restriction des préconditions sur this, et donc on viole le LSP Je pousse ton raisonnement à l'extrême, mais c'est pour essayer de t'amener à changer ta vision sur le LSP, qui pour moi doit toujours s'envisager par rapport au contexte réel (où il n'y a aucun problème à restreindre la précondition sur this, puisque les mécanismes du langage nous offrent les garanties nécessaires --> influence du contexte sur le LSP).
    Tu ne pousse pas mon raisonnement à l'extrême, tu le déforme complêtement:

    Il est admis qu'une fonction ait un comportement adapté pour les classes dérivées, pour autant que le comportement soit cohérent avec ce que l'on attend de la fonction, cela n'invalide absolument pas une propriété de la classe de base.

    Par contre, le fait de restreindre l'accessibilité d'une fonction dans la classe dérivée par rapport à la classe de base le fait effectivement: la classe de base accepte que l'on appelle une fonction donnée alors que la classe dérivée ne l'accepte (théroiquement) pas.
    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. #57
    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 koala01 Voir le message
    Pas du tout: pour la classe dérivée, foo est bel et bien à usage interne uniquement...
    C'est ton point de vue. Pas le mien. On ne peut pas connaitre le rôle et l'usage d'une fonction dans une classe dérivant d'une autre sans en regarder la classe de base. Et dans notre cas, foo redéfini une fonction de la classe de base, elle n'est donc pas à usage interne, et pourra être appelée depuis n'importe quelle fonction définie au niveau de la classe de base (ce qui serait le cas même si foo était privée aussi au niveau de la classe de base). A partir du moment où la classe de base y a accès, elle décide si elle expose cet accès à l'extérieur, par exemple en rendant la fonction publique, ou par l'intermédiaire du NVI. Si tu voulais avoir une fonction réellement à usage interne, tu n'avais qu'à par redéfinir une fonction de la classe de base.

    Si je résume ton propos, tu dis : Ma fonction est privée, donc à usage interne, le fait qu'elle redéfinisse une fonction virtuelle casse ça, donc il y a un problème de LSP.

    Je dis : Ta fonction redéfini une fonction virtuelle, donc dans le respect du LSP, elle n'est pas à usage interne uniquement. Si on l'a mise en privée, c'est pour une raison autre que celle de la restreindre à un usage interne (puisqu'elle n'est pas restreinte) (quelle raison ? là ne ne sais pas trop), et il y a un problème si c'est ce qu'on voulait faire.

    Citation Envoyé par koala01 Voir le message
    Encore une fois, tu sembles estimer qu'il n'y a pas d'erreur dans le fait de décider de l'héritage, alors que l'exécution même te montre que tu as effectivement fait une erreur à la base en décidant que Derivee hérite de Base.
    Elle montre une erreur, mais pas dans l'héritage. L'erreur est de croire que privé signifie "à usage interne".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class A
    {
      virtual void doF() = 0;
    public:
      void f() {doF();}
    };
     
    class B: public A
    {
      private: virtual void doF() {/*...*/}
    };
    B::doF a beau être privée, elle n'est pas réservée à l'usage interne de B, et j'irai même plus loin : Elle n'est pas accessible en interne à B...

    privée hors virtualité signifie : "à usage interne"
    privée plus virtualité signifie : "à l'usage des classes de base"

    Citation Envoyé par koala01 Voir le message
    Mais que l'on accepte que Derivee hérite de Base alors qu'une des propriétés vérifiées de Base (le fait de pouvoir appeler foo depuis n'importe où) ne le soit pas pour Derivee, là, je dis NON: Problème de conception.
    Pourquoi diable ne pas accepter l'idée que le seul fait d'accepter que Derivee hérite de Base est une erreur de conception LSP le met pourtant clairement en évidence: une des propriétés vérifiées pour la classe de base n'est pas vérifiée pour la classe dérivée, et donc, on ne peut pas envisager l'héritage public, point-barre.
    Tout comme le Tentacule Blanc, je peine à voir quelle serait cette propriété. Si on tente d'appliquer le principe trop à la lettre, on tombe forcément sur des non sens (soit A et B dérivant publiquement de A, soit a1 et a2 des variables de type A, j'ai la propriété typeid(a1).name() == typeid(a2).name(). Si je remplace a1 par b1, instance de B, cette propriété n'est plus vérifiée, donc le LSP n'est pas respecté en C++, même avec des classes vides !!). Si on l'applique plus librement, je ne vois aucune violation.
    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.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Si je résume ton propos, tu dis : Ma fonction est privée, donc à usage interne, le fait qu'elle redéfinisse une fonction virtuelle casse ça, donc il y a un problème de LSP.
    Ce n'est pas avec le fait qu'elle redéfinisse une fonction virtuelle que j'ai un problème, c'est le fait qu'elle redéfinisse une fonction... publique et qu'elle en restreigne l'accessibilité (ou du moins, qu'elle soit sensée le faire) qui me pose problème.
    Je dis : Ta fonction redéfini une fonction virtuelle, donc dans le respect du LSP,
    Non, parce que, en vertu de LSP, elle ne devrait même pas pouvoir redéfinir cette fonction, vu que tout LSP nous incite à refuser la possibilité même de faire hériter la classe dérivée de la classe de base dans cette situation.

    Elle montre une erreur, mais pas dans l'héritage. L'erreur est de croire que privé signifie "à usage interne".
    Si elle montre une erreur dans l'héritage, du seul fait qu'une propriété valide pour la classe de base devient invalide pour la classe dérivée...

    Tout le reste découle de là.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class A
    {
      virtual void doF() = 0;
    public:
      void f() {doF();}
    };
     
    class B: public A
    {
      private: virtual void doF() {/*...*/}
    };
    B::doF a beau être privée, elle n'est pas réservée à l'usage interne de B, et j'irai même plus loin : Elle n'est pas accessible en interne à B...

    privée hors virtualité signifie : "à usage interne"
    privée plus virtualité signifie : "à l'usage des classes de base"
    Ici, c'est la capacité à appeler f() depuis l'extérieur de la classe qui est la propriété valide de la classe de base, qui reste valide pour la classe dérivée.

    Le fait que f appelle en interne une fonction "à usage interne", fusse-t-elle redéfinie pour s'adapter à ce que l'on attend dans le cas de la classe dérivée, n'invalide absolument pas la capacité que l'on a d'appeler f depuis... un objet du type de la classe dérivée et n'est en définitive, qu'un "détail d'implémentation"...

    Comme je l'ai déjà dit, si je peux concevoir (à condition que l'on me donne des arguments cohérent) que l'on puisse retirer des restrictions d'accès à une fonction redéfinie dans la classe dérivée (en la faisant passer de privée / protégée à publique), je ne conçois absolument pas que l'on puisse placer des restrictions à une fonction publique.

    Le concept même d'héritage et de polymorphisme est basé sur LSP: quand une classe hérite d'une autre, je peux transmettre un objet du type dérivé à une fonction qui demande une instance (sous la forme de référence ou de pointeur ) du type de base.

    Je pourrai alors manipuler mon objet de type dérivé dans cette fonction exactement comme je manipulerais un objet du type de base et j'obtiendrai un comportement cohérent par rapport au type dérivé.

    C'est ca, le raisonnement suivi par le polymorphisme, on est bien d'accord

    Seulement, pour que cela marche, il faut que la décision de faire hériter la classe dérivée de la classe de base soit cohérente, et, pour cela, que LSP soit validé en s'assurant que toute propriété valide pour la classe de base soit également valide pour la classe dérivée.

    Par propriété, il faut comprendre la possibilité d'accéder à l'ensemble de ce qui est publique dans une classe.

    Si l'on passe une des fonctions publiques de la classe de base dans une accessibilité moindre, on invalide de facto la propriété associée (vu que l'on ne peut pas dire, en regardant uniquement la classe dérivée, que l'on peut appeler la fonction ainsi redéfinie "depuis l'extérieur"), et LSP doit donc nous nous inciter à renoncer à l'héritage public, où à envisager un "turn arround".
    Tout comme le Tentacule Blanc, je peine à voir quelle serait cette propriété. Si on tente d'appliquer le principe trop à la lettre, on tombe forcément sur des non sens (soit A et B dérivant publiquement de A, soit a1 et a2 des variables de type A, j'ai la propriété typeid(a1).name() == typeid(a2).name(). Si je remplace a1 par b1, instance de B, cette propriété n'est plus vérifiée, donc le LSP n'est pas respecté en C++, même avec des classes vides !!). Si on l'applique plus librement, je ne vois aucune violation.
    Là, tu confond la propriété "accepte que l'on demande le nom du type" avec le résultat obtenu, résultat qui s'adapte aux besoins de la classe dérivée, ce qui est tout à fait normal et attendu.

    Ce n'est pas parce que le résultat est différent en terme de valeur et qu'une égalité sur cette valeur devient invalide que tu invalides la propriété, car:
    1. la valeur obtenu est adaptée au type réellement manipulé ( ce n'est que du polymorphisme classique)
    2. la propriété qui consiste à être capable de demander le nom de la classe reste tout à fait valide pour la classe dérivée.
    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

  19. #59
    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 koala01 Voir le message
    Là, tu confond la propriété "accepte que l'on demande le nom du type" avec le résultat obtenu, résultat qui s'adapte aux besoins de la classe dérivée, ce qui est tout à fait normal et attendu.

    Ce n'est pas parce que le résultat est différent en terme de valeur et qu'une égalité sur cette valeur devient invalide que tu invalides la propriété, car:
    1. la valeur obtenu est adaptée au type réellement manipulé ( ce n'est que du polymorphisme classique)
    2. la propriété qui consiste à être capable de demander le nom de la classe reste tout à fait valide pour la classe dérivée.
    Je ne confonds rien. Je veux juste prouver par l'absurde que la formulation stricte du LSP utilisée dans cette discussion (qui dit qu'il serait violé chaque fois qu'une propriété quelconque prouvable d'un objet du type de base ne l'est plus pour un type dérivé) est peu intéressante en pratique, et que donc je préfère utiliser la définition non stricte (là où j'utilise un type de base, je peux utiliser un type dérivé) et cette seconde définition marche avec le passage en privé de la fonction redéfinie.

    Je veux bien raisonner à partir d'une autre définition plus stricte du LSP, mais pas celle donnée jusqu'à présent.
    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.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Je ne confonds rien. Je veux juste prouver par l'absurde que la formulation stricte du LSP utilisée dans cette discussion (qui dit qu'il serait violé chaque fois qu'une propriété quelconque prouvable d'un objet du type de base ne l'est plus pour un type dérivé) est peu intéressante en pratique, et que donc je préfère utiliser la définition non stricte (là où j'utilise un type de base, je peux utiliser un type dérivé) et cette seconde définition marche avec le passage en privé de la fonction redéfinie.
    Si ce n'est que tu essaye de comparer le résultat d'une propriété prouvable, et qu'il est tout à fait logique que ce résultat soit adapté à partir du moment où le comportement a lui-même été adapté.
    Je veux bien raisonner à partir d'une autre définition plus stricte du LSP, mais pas celle donnée jusqu'à présent.
    Le fait est que tu sembles estimer que la décision d'utiliser un héritage était à la base justifiée.

    Or, ma thèse est, justement, que la décision elle-même n'est absolument pas opportune et que LSP nous aide à nous en rendre compte, en l'appliquant de manière stricte:

    Lorsque tu décides de faire hériter une classe B d'une classe A, tu commence par vérifier si les différentes propriétés (comprend: les différentes fonctions publiques) valides pour la classe A sont également valides pour la classe B.

    Or, tu te rend compte qu'une des fonctions virtuelles publiques dans la classe A subit une restriction dans la classe B.

    Tu as donc trois solutions:
    1. Soit tu estimes (sans doute au vu des autres classes qui hériteront de A) que la fonction redéfinie sera, effectivement, à "usage interne" des différentes classe, y compris pour A, et tu envisageras dés lors d'appliquer la même restriction à la classe A qu'à la classe B.
    2. Soit au contraire, tu estimes (toujours sans doute au vu des autres classes qui hériteront de A), qu'il n'y a en réalité aucune raison de restreindre l'accès à la fonction dans les classes dérivées, et tu décide donc de redéfinir la fonction en tant que publique dans B.
    3. Soit, enfin, tu estimes qu'il y a, effectivement, des raisons pour que cette fonction soit publique dans A et protégée (privée) dans B. Si tu veux effectivement disposer d'une base commune pour ces deux classes, il faudra les faire hériter d'une classe qui présente "la partie communes aux deux interface publiques, c'est à dire les fonctions qui sont publiques dans les deux cas (fussent-elles redéfinies pour les différentes classes), en tenant cependant compte du fait que, lorsque les instances de A et de B passeront pour être du type de cette classe supplémentaire, tu ne pourra de toutes façons pas invoquer la fonction, étant donné qu'elle ne fait pas partie de l'interface commune.
    Le fait d'admettre l'héritage malgré tout représente bel et bien un problème de conception, mis en évidence par... une application stricte de LSP...

    Je ne comprend décidément pas ton obsession à vouloir accepter cet héritage dont l'application stricte de LSP t'indique qu'il n'a vraiment pas lieu d'être.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Réponses: 2
    Dernier message: 02/10/2008, 16h37
  2. Fonction appelant une fonction virtuelle pure
    Par oodini dans le forum C++
    Réponses: 12
    Dernier message: 19/09/2008, 08h24
  3. Une fonction virtuelle ne peut pas retourner un template!
    Par coyotte507 dans le forum Langage
    Réponses: 10
    Dernier message: 08/02/2008, 20h39
  4. Problème de visibilité d'une fonction
    Par hello2lu dans le forum VBA Access
    Réponses: 8
    Dernier message: 03/07/2007, 15h20
  5. Réponses: 2
    Dernier message: 05/03/2006, 19h29

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