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. #81
    Membre très actif Avatar de metagoto
    Profil pro
    Hobbyist programmateur
    Inscrit en
    Juin 2009
    Messages
    646
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Le principe dit pour tous les programmes. Donc cela ne devrait pas suffire à déduire notre (juste) cause
    Tous les programmes c'est quand on cherche à caractériser S comme étant ou non sous type de T. (LSP version 1988)

    Ici nous posons S comme étant un sous type de T (LSP version 1994) pour opérer le changement de visibilité.

    Pour ca on est tous d'accord je crois mais si la faute n'est pas un viol du LSP, où est le biais ? Quel loi/principe est violé ?
    Je pense que les postes de koala01 nous éclairent à ce sujet, en tous cas pour ce qui est des biais

  2. #82
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Citation Envoyé par metagoto Voir le message
    Tous les programmes c'est quand on cherche à caractériser S comme étant ou non sous type de T. (LSP version 1988)

    Ici nous posons S comme étant un sous type de T (LSP version 1994) pour opérer le changement de visibilité.


    Je pense que les postes de koala01 nous éclairent à ce sujet, en tous cas pour ce qui est des biais
    Les deux énoncés du LSP supposent le sous-typage. Dans la théorie de Liskov le lien de sous-typage est choisie par l'utilisateur et non déduit. Et c'est justement le but du LSP de vérifier si ce choix est ou non valide.

    La première dit qu'on l'ont doit pouvoir utiliser un objet d'un type fille partout ou l'on attend un objet de type mère.
    La seconde dit que les propriétés valable sur les objets de type mère doivent aussi être valable sur les objets de type fille.
    Dans les deux cas le sous-typage est supposé. La formulation formelle étant la seconde (celle à préférer AMHA )

    Les postes de koala visent juste à démontrer que le LSP est violé, si ils t'éclairent d'une autre manière, je serais heureux de savoir comment ?

    <HS>On ne dit pas biais ?</HS>

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

    Informations professionnelles :
    Activité : Hobbyist programmateur

    Informations forums :
    Inscription : Juin 2009
    Messages : 646
    Par défaut
    Les def sont rappelée ici:
    http://www.developpez.net/forums/d95...p/#post5380289

    La première definition du LSP dit que si on peut pour tout programme opérer les substitutions et que son comportement est inchangé, alors S est un sous type de T.

    La seconde suppose ce sous typage puis explicite les contraintes.

    Les postes de koala visent juste à démontrer que le LSP est violé, si ils t'éclairent d'une autre manière, je serais heureux de savoir comment ?
    Que c'est un très mauvais design. Lorsqu'on manipule l'objet dans un autre context qu'une substitution (pas de polymorphisme) l'interface publique n'est plus la même que sa base. De quoi se poser la question de la pertinence de l'héritage publique (Annimal qui dérive de Voiture, à moins que ce ne soit l'inverse? )

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Tu ne peux pas, car les mécanismes intrinsèques du langage vont introduire des comportements, comportements qui sont forcément à intégrer dans ta conception. L'idée d'une conception indépendante de tout langage n'existe que parce qu'il y a beaucoup de similitudes entre les langages. Tant que tu restes dans ce carcan de similitudes, tu peux être "indépendant du langage". Ici, on est clairement dans quelque chose de propre à C++.
    Même pâs... Java et d'autres permettent de définir l'accessibilité des fonctions membres.

    ET quand bien même on pourrait considérer qu'il s'agit d'un problème clairement propre à C++, il faut envisager le fait que c'est, avant tout, une question de conception.

    De ce fait, même s'il faut, effectivement, envisager les particularités de C++, il faut, comme je l'ai expliqué, que les règles de bases qui, elles, sont indépendantes du langage, soient validées avant de s'inquiéter des opportunités offertes par le langage.
    Le langage n'offre pas que des restrictions. Il offre des possibilités. La conception est nécessairement fonction de ces différentes possibilités (on ne conçoit pas de la même manière si on cible Ocaml ou java).
    Non, ce sont les types de langage, les paradigmes admis par ceux ci qui offrent les possibilités.

    Et il est donc vrai que l'on ne conçoit pas pour un type de langage donné (pour un langage proposant un paradigme donné) de la même manière que ce que l'on fait pour un autre.

    Si l'on ne conçoit pas pour java comme on le fait pour Ocalm, c'est, uniquement, parce que les paradigmes utilisés sont différents.

    Mais on concevra de manière identique pour n'importe quel langage orienté objet: une conception pour C++ peut être utilisée pour java mais risque de nécessiter certains aménagement du fait des restrictions imposées par ce dernier.
    On ne parle pas de dire que c'est autorisé par le langage donc c'est bien. On parle de dire que dans le contexte lié à ce langage, telle chose est correcte et ne pose pas de problème particulier.
    Mais, avant d'envisager le contexte d'un langage particulier, il y a une barrière à passer qui nous bloque déjà bien avant: la barrière du respect des principes généraux de la conception orientée objet.

    Dans le cas présent, l'énoncé même de liskov nous dit qu'il y a "un problème quelque part" parce qu'une propriété valide pour la classe de base ne l'est pas pour la classe dérivée.

    A partir de là, il n'est même pas nécessaire d'aller voir ce qu'en pense le compilateur ou l'interpréteur de n'importe quel langage: aucun langage n'est en mesure de faire qu'une mauvaise décision devienne bonne "comme par la magie du langage".

    Par contre, si les principes généraux de base sont respectés, il faut, effectivement considérer le langage envisagé pour déterminer s'il n'invalide pas une décision validée par les principes généraux, car, les restrictions imposées par certains langages peuvent effectivement nous interdire le recours à une technique conceptuellement correcte.
    Je vais te donner un dernier exemple. Suppose que tu veuilles modéliser un graphe. En java ou C#, tu sais que tes objets sont garbages collectés. Tu vas donc pouvoir modéliser ton graphe sous la forme d'objets noeuds, qui possèdent chacun une liste d'autres objets noeuds atteignables. Et tu ne te préoccupes absolument pas de savoir quand un noeud doit être détruit, car il sera de toute manière garbage collecté. En C++, tu ne peux pas passer sur cette contrainte, tu es obligé de revoir ta conception. Pourquoi ? Parce que le contexte lié au langage est différent, et qu'il amène des mécanismes qui font qu'une même conception est valable pour l'un et pas pour l'autre.
    Non, c'est au moment où tu décide de la manière d'implémenter la logique interne des détails d'implémentation de ton graphe que tu devra prendre en compte que le langage n'est pas garbabe collecté.

    Le DP composite, qui est la base des graphes, sera le même pour C++ que pour C# ou java.

    La présence ou l'absence d'un garbage collector n'interviendra qu'au moment où tu définira la politique de destruction des objets.
    La conception indépendante de tout langage, c'est à mon avis une belle illusion, qui s'effrite d'autant plus que tu pratiques des langages éloignés les uns des autres. Même si certains principes sont universels, leur application peut différer.
    Justement, non...

    Il est clair qu'il ne faut pas vouloir appliquer les principes de conception OO à des langages fonctionnels, et je veux même bien admettre que certains langages OO prennent certaines liberté dans leur philosophie par rapport à certains principes (ou, au contraire, apportent certaines restrictions par rapport à d'autres), les principes qui te permettent de prendre les décisions de conception restent la seule base solide commune à tout langage proposant un paradigme commun.

    On pourrait donc, effectivement, discuter de l'opportunité du choix effectué par java ou autre de recourir à un super objet du point de vue de LSP ou de demeter, mais ce serait un autre débat.

    Par contre, que ce soit en java, en C# ou en C++, la décision de faire hériter une classe d'une autre est systématiquement bloquée par Liskov si elle est inopportune au moment où tu te pose la question fatidique "puis-je réellement estimer qu'un objet de la classe XXX est-un objet de la classe YYY".

    Si Liskov accepte que l'on envisage l'héritage, tu peux (car tu n'y es malgré tout pas obligé) envisager de faire hériter ta classe de l'autre, autrement, il ne sert à rien d'essayer d'aller plus loin.

    Citation Envoyé par metagoto Voir le message
    Non seulement ça compile, mais surtout ça fonctionne. La propriété du programme est toujours vraie après avoir effectué la substitution de type.
    Mais cela ne fonctionne pas de la manière attendue.

    Quand tu vois une fonction protégée ou privée, tu t'attends à ne pas pouvoir l'appeler d'une autre manière que... depuis une fonction membre de la classe (ou depuis une fonction membre des classes dérivées pour les fonctions protégées).

    Or, le fait de disposer de la fonction en accessibilité publique dans une classe de base retire justement cette restriction pourtant importante en permettant d'invoquer la fonction depuis... n'importe où, y compris depuis des fonctions qui ne sont absolument pas membres d'une classe intervenant dans la hiérarchie de classe.

    Nous sommes bel et bien face à quelque chose qui est accepté par le langage, mais qui ne permet malgré tout pas de rendre "par la magie du langage" une décision erronée valide.

    Cette particularité ne doit pas nous inciter à accepter l'héritage de cette manière, mais devrait plutôt nous inciter à réfléchir au niveau du langage à la possibilité qu'il pourrait y avoir à... éviter cette situation.
    Donc il existe au moins un programme dans lequel un changement de visibilité ne viole pas LSP. Cela suffit à en déduire que le changement de visibilité n'est pas une condition suffisante.

    Ca ne dit rien d'autre que ça.
    Cela ne dit absolument pas ca...

    Cela dit que tu est face à une aberration u langage, à un bug du compilateur, à une impossibilité de je ne sais pas quoi, mais pas que ce soit valide du point de vue de la conception.

    D'abord, tu occulte déjà le fait que le résultat à l'exécution est totalement illogique (cf le code que j'ai proposé précédemment), mais ensuite, tu estimes que, parce que cela compile et que cela fonctionne, ce ne peut absolument pas être en contradiction avec LSP.

    Or le fait que cela compile et que cela fonctionne (et surtout que cela fonctionne de manière illogique) est tout sauf une preuve du respect de LSP:

    C'est une aberration (je ne trouve pas d'autre terme) du langage, un bug du compilateur ou tout ce que tu veux, mais, surement pas une preuve de ce que tu avances.

    Le fait de vouloir imposer ce comportement particulier comme preuve revient exactement au même que celui de vouloir imposer le fait que ton compilateur considère qu'un pointeur est codé sur un unsigned long parce que c'est ce qu'indique sizeof(int *) sur ta machine.

    Comme je l'ai dit, il se fait que, chez moi, sizeof(int*) s'apparente... à un unsigned long long.
    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. #85
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Citation Envoyé par metagoto Voir le message
    Les def sont rappelée ici:
    http://www.developpez.net/forums/d95...p/#post5380289

    La première definition du LSP dit que si on peut pour tout programme opérer les substitutions et que son comportement est inchangé, alors S est un sous type de T.

    La seconde suppose ce sous typage puis explicite les contraintes.
    Mais, justement:

    Si tu te base sur la première, le comportement est modifié, car tu peux appeler une fonction que tu n'aurais jamais du pouvoir appeler

    Si tu te base sur la deuxième, les contraintes pour accepter l'héritage ne sont pas respectées, en invalidant une des propriétés de la classe de base dans la classe dérivée.

    Quel que soit l'angle sous lequel tu regarde les choses, LSP te pousse à refuser l'idée même de l'héritage dans le cas présent.
    Que c'est un très mauvais design. Lorsqu'on manipule l'objet dans un autre context qu'une substitution (pas de polymorphisme) l'interface publique n'est plus la même que sa base. De quoi se poser la question de la pertinence de l'héritage publique (Annimal qui dérive de Voiture, à moins que ce ne soit l'inverse? )
    Le seul fait que tu t'interroge sur la pertinence de l'héritage publique montre que tu te rend compte qu'il y a un problème quelque part.

    Or, il se fait que c'est justement LSP qui te prouve qu'il y en a effectivement un, parce que, si tu l'avais appliqué directement stricto sensu, tu n'aurais jamais accepté l'héritage.

    LSP est la base sur laquelle te reposer pour décider d'accepter un héritage publique. Si, à un moment où un autre, tu viens à douter de la pertinence de l'héritage, c'est à tous les coups parce que tu ne l'as pas respecté ou que tu n'y a pas été attentif au moment où tu as pris la décision d'y recourir.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #86
    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 : 50
    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
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Même pâs... Java et d'autres permettent de définir l'accessibilité des fonctions membres.
    De manière assez différente, quand même. En particulier, en C++ accessibilité et l'héritage sont assez décorrélés. Essaye d'avoir une fonction privée virtuelle en C#, ce n'est pas possible. Donc le pattern de conception NVI est possible en C++ (et je l'emploie quasi systématiquement, et regrette généralement ensuite le "quasi"), mais impossible dans C# (et probablement Java, mais je connais moins). Alors tu peux dire qu'il s'agit de détails de conception, et non de conception générale, mais tout de même...
    Citation Envoyé par koala01 Voir le message
    Mais on concevra de manière identique pour n'importe quel langage orienté objet: une conception pour C++ peut être utilisée pour java mais risque de nécessiter certains aménagement du fait des restrictions imposées par ce dernier.
    Je suis assez fortement en désaccord là dessus. Comme le dit Coplien dans "Multiparadigm design for C++", il y a plusieurs façon possibles d'effectuer l'analyse de commonalités et de variabilité de l'espace du problème, et on peut trouver plusieurs jeux de dimensions différents, incompatibles, et a priori aussi valables les uns que les autres. Sauf qu'au moment de l'analyse de l'espace de la solution, certains choix vont être plus ou moins aisément mettables en œuvre dans le langage cible, et donc il recommande d'avoir une bonne idée de ces choix lors de l'analyse de l'espace du problème, afin de choisir le jeu de dimensions le plus adapté.

    Si j'essaye d'être plus concret, en Java 1.4 ou en C#1.0, la bonne manière de designer des collections était un super-objet. Et il serait absurde si l'on sait que l'on doit travailler dans ces langages de faire une analyse à la C++ du problème (détermination des concepts auxquels doivent répondre les objets pour être membre d'un conteneur) pour finalement dire qu'avec des restrictions de ces langages, l'analyse ne sert à rien, puisque de toute façon, on n'a dans ces langages aucun outil qui manipule ces concepts. Il ne s'agit pas de dire que ces langages implémentent la même chose, avec des détails dont il faudra ne tenir compte que dans une phase ultérieure, mais que ces langages ont outils qui permettent de mettre en place des conceptions de nature fondamentalement différentes.

    Citation Envoyé par koala01 Voir le message
    Le DP composite, qui est la base des graphes, sera le même pour C++ que pour C# ou java.
    Très bon exemple (j'aurais plutôt dit arbres que graphes, mais là n'est pas la question) ! En effet, un design en C++ va justement s'efforcer de s'éloigner de ce DP, pour revenir aux fondamentaux mathématiques de cette structure d'arbre, afin de fournir des algorithmes qui soient génériques, en se basant sur la notion de concept (et pas du tout sur des classes de base, ou de la POO). C'est ce que fait boost::graph pour les graphes, en s'abstrayant de savoir si on a une liste de nœud liés, une liste de liens vers d'autres nœuds, un tableau d'adjacence... Un tel design n'aurait pas trop de sens en C#.
    Citation Envoyé par koala01 Voir le message
    Quand tu vois une fonction protégée ou privée, tu t'attends à ne pas pouvoir l'appeler d'une autre manière que... depuis une fonction membre de la classe (ou depuis une fonction membre des classes dérivées pour les fonctions protégées).
    Ou depuis une fonction membre de la classe de base si cette fonction est virtuelle. J'insiste car c'est je pense le plus courant dans mes programmes. Il est très rare qu'une fonction virtuelle et privée soit appelée par une autre fonction de la même classe. Le plus souvent, une telle fonction dans le cadre du NVI est appelée depuis la fonction publique de la classe de base.
    Citation Envoyé par koala01 Voir le message
    Or, le fait de disposer de la fonction en accessibilité publique dans une classe de base retire justement cette restriction pourtant importante en permettant d'invoquer la fonction depuis... n'importe où, y compris depuis des fonctions qui ne sont absolument pas membres d'une classe intervenant dans la hiérarchie de classe.
    C'est là que je suis en désaccord. On ne peut pas l'appeler depuis n'importe où. On peut l'appeler depuis les endroits où l'on sait que la classe dérive de la classe de base qui défini cette fonction publique.

    Si une classe A implémente les interfaces I1 et I2, que I1 défini en publique une fonction virtuelle (ce qui est probablement un mauvais choix, dans le cadre d'un langage qui permet le NVI, mais un mauvais choix au niveau de la conception de I1. Si I1 est défini dans une bibliothèque, lorsque je fais la conception de A et I2, je dois vivre avec ce choix, et trouver la meilleure conception possible dans ce périmètre), mais que je souhaite que les gens qui manipulent directement A n'aient pas accès à la fonction, la définir privée dans la classe dérivée me semble un bon choix de conception.

    D'autres langages définissent même des fonctionnalités spécifiques pour mettre ça en place, là où en C++ les règles générales marchent, ce qui semble vouloir dire que ce concept n'est pas si mauvais. Je pense par exemple au C# où il est possible de dire qu'une classe va implémenter explicitement une interface, c'est à dire qu'elle va redéfinir les fonctions (forcément publiques) de cette interface, sans pour autant que ces fonctions soient disponibles pour un utilisateur de la classe qui ne passerait pas par l'interface. Pour qu'un langage soit allé jusqu'à prévoir une syntaxe spécifiquement pour, on peut imaginer que le besoin existe, non ?
    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.

  7. #87
    Membre Expert
    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
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Pour les paramètre covariant, il me semble que les langages qui les permettent par defaut utilsent un typage différent de celui de Liskov
    La notion de dispatch est celle de choisir la fonction appelée en fonction du type réel de l'objet, et non de son type statique. Et je ne sais pas ce que c'est qu'un typage de Liskov (première fois que j'entends ça).

    Sinon montre moi une diagramme de classe qui met en oeuvre une technique pour résoudre un problème dispatch et montres moi la violation du LSP.
    C'est assez simple de mettre en évidence la différence. Mais ça ne sert absolument à rien de se préoccuper des mécanismes grâce auxquels tu fais le multiple dispatch (sauf si ces mécanismes introduisent des différences sémantiques, mais on ne va pas chipoter c'est hors propos ici) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct A { virtual void f(A& a) {...};
    struct B : public A {
    void g() { ... };
    virtual void f(B& b) { b.f(); }; // certainement pas du c++
     
    main()
    {
        A* a1 = new B();
        A a2;
        a1.f(a2);
    }
    en single dispatch, c'est B::f qui est appelée, avec un A comme paramètre --> ça se vautre lamentablement à l'exécution, et pour cause, les préconditions de B::f sont plus restreintes que celles de A::f (et ça, Liskov nous a dit que c'était très mal).

    en multiple dispatch, c'est A::f qui est appelée (la signature de B::f ne permet pas l'appel, car a2 n'est pas un B). L'appel fonctionne, et fait ce qui est attendu. Et ça, Liskov ne nous a jamais dit que c'était mal.

    Et si tu penses tout de même qu'il y a violation du LSP parce que B::f prend un B alors que A::f prend un a, je t'invite à utiliser une notion objet type Ada (où l'objet est le premier paramètre de la fonction), qui donnerait :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     // certainement pas du c++
    struct A { virtual void f(A* this, A* a); }
    struct B : public A
    { virtual void f(B* this, B* a); };
    Et de te poser sérieusement la question de, en multiple dispatch, qu'est-ce qui différencie le premier paramètre du deuxième ? Et de comment la covariance du premier pourrait ne pas être une violation du LSP alors que la covariance du second en serait une ?

    (Si je prend les dispatchers de Loki, à part ta hiérachie qui doit respecter le LSP mais n'a pas de lien direct avec le dispatcher, aucune autre classe ne doit être hérité, donc pas de viole du LSP).
    Il faudrait que je relise ce chapitre, mais j'ai du mal à voir de quoi tu parles. S'il n'y a pas de hiérarchie/d'héritage, il n'y a pas de dispatch.

    Koala n'a pas dit qu'il n'y avait pas de cadre à l'application du LSP, mais qu'on ne devait pas tenir compte du langage. La seul restriction pour l'appliquer ca serait quelque chose comme : langage objet et typage selon la théorie de Liskov.
    Tu peux ne pas tenir compte du langage si tu veux. Tu ne peux pas faire l'impasse sur le contexte de tes substitutions. En général, ce contexte est amené par ton langage (même si, effectivement, tu peux dans un langage assez libre recréer ce contexte, cf les gens qui font de l'objet en C).

    Après le langage peut apporter certains restrictions (comme l'héritage multiple pour java), mais je vois difficilement comment il pourrait élargir des principes (ca serait plus un principe sinon).
    Le langage n'apporte pas des restrictions, il apporte un contexte. Ce contexte peut-être plus restreint que celui que tu as utilisé pour ta conception (héritage multiple en java) ou plus étendu (granularité de la visibilité des membres en eiffel qui n'est pas à ma connaissance transcriptible en UML).

    Le langage n'élargit pas le principe, il change simplement le contexte. Et la validité du principe ne saurait s'apprécier que dans un contexte donné (sinon, on peut dire tout et son contraire). Si je change le contexte, des mécanismes similaires au premier abord, mais dont la manifestation réelle est différente dans chacun des contextes, peuvent se retrouver à l'un respecter un principe et l'autre non (cf plus haut sur la covariance des paramètres)

    C'est justement ce que koala essay d'expliquer, cette problématique ne peut arriver car la question doit se poser au moment de la conception et cette volonté écarté. Si ta question est : est ce que je peus compiler sans problèmes en restreignant la visibilité ? alors la réponse est oui. Mais ce n'est pas la question, la question est bien de savoir si une restriction de visibilité fait sens au niveau du LSP ou non.
    Le principe d'un raisonnement par l'absurde, c'est de prendre la propriété qu'on pense fausse comme hypothèse, et de montrer qu'on arrive à un résultat incohérent/faux. Ici, en prenant comme hypothèse : je peux changer la visibilité d'une fonction dans une classe dérivée, force est de constater qu'avec les mécanismes de substitution de C++ (qui sont des hypothèses de mon problème initial, je ne peux pas faire l'impasse dessus), il n'y a pas de problème.

    Citation Envoyé par Flob90 Voir le message
    Pour ca on est tous d'accord je crois mais si la faute n'est pas un viol du LSP, où est le biais ? Quel loi/principe est violé ?
    Il n'y a pas besoin de violer des principes pour faire une mauvaise conception . Si faire une bonne conception se résumait à respecter des principes, ce serait à la portée du premier étudiant venu. Force est de constater qu'au contraire, c'est un exercice très difficile, et qu'il demande aussi beaucoup d'expérience et une bonne intuition. Violer un principe est un indicateur d'une mauvaise conception, les respecter n'indique rien du tout en soi (condition nécessaire mais certainement pas suffisante).

  8. #88
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Sous-typage de Liskov ou de Cook, Eiffel est basé sur celui de Cook. J'ai plus la référence, mais il y a un article sur un blog (en anglais) qui explique justement la nécessité de ce type de typage pour le multi-dispatching naturel. (il montre avant la faiblesse de celui de Liskov) J'ai toujours eu l'impression que le LSP était lié à ce type de typage, mais je peus méprendre (je n'ai jamais expérimenté les différence de ces deux théories, juste lu un ou deux articles à l'occasion)

    Tes raisonnement avec this se basent aussi sur le langage, en termes de POO tu définis des classes qui peuvent recevoir des messages, que le langage utilise un mécanisme via this importe peu. (pour le moment le seul endroit ou je vois l'influence de this, ou similaire pour d'autre langage, est un article de Deloget ou il le cite pour justifier un point de LOD)

    Pour loki, tu as bien une hiérachie. Mais elle ne contient généralement pas la fonction à dispatcher. Les différentes versions de la fonction sont dans une autre classe, et le dipatcher se base sur une typelist de ta hiérachie et la classe contenant toutes les fonctions possibles.

    Pour le principe du raisonnement par l'absurde, je suis d'accord (j'aurais du mal à soutenir que ce n'est pas ca ). C'est aussi ce que koala a fait, il se demande si l'héritage avec changement de virtualité est justifié, et il applique le LSP (violé) et montre que non. Le raisonnement que vous faites montre (c'est un avis personnel ) juste que le C++ n'impose pas le LSP : vous supposez qu'il l'impose, vous faite un programme qui ne le respecte pas et il compile et fonctionne, conclusion le C++ n'impose pas le LSP. Je suis d'accord avec votre raisonnement de manière général mais pas l'hypothèse que vous faites.

    Si tu me montres une conception qui répond à un problème sans violer aucun principe alors je ne pourrais pas te dire que la conception en soit est mauvaise, par contre elle peut-etre moins efficase qu'une autre.

    NB: Je comprend parfaitement ton point de vue, si tu remontes les messages tu verras que j'ai été d'accord à un moment (pas mon premier message mais le suivant), mais je trouve 'largumentation de koala vraiment pertinante. (du coup j'ai du mal à trouver d'autre argument que les siens, d'ou une petite inprésion de répétition, aussi pour une argumentation que pour l'autre )

  9. #89
    Rédacteur
    Avatar de pseudocode
    Homme Profil pro
    Architecte système
    Inscrit en
    Décembre 2006
    Messages
    10 062
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Architecte système
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2006
    Messages : 10 062
    Par défaut
    La question posée dans cette discussion est :
    Le changement de visibilité d'une fonction virtuelle est-il un viol du LSP ?
    Le LSP n'a de sens que si on veut mettre en oeuvre la notion de "Behavioral Subtyping", c'est à dire la spécialisation du comportement.

    Si le développeur souhaite mettre en oeuvre cette notion et spécialiser le comportement de sa classe, alors il ne doit pas réduire la visibilité des méthodes.

    Par contre, le développeur peut très bien décider le Subtyping pour autre chose : réutilisation de code, changement de contrat, ... Auquel cas il ne viole pas le LSP, puisqu'il n'est pas applicable dans ce contexte.
    ALGORITHME (n.m.): Méthode complexe de résolution d'un problème simple.

  10. #90
    Membre Expert
    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
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    Sous-typage de Liskov ou de Cook, Eiffel est basé sur celui de Cook. J'ai plus la référence, mais il y a un article sur un blog (en anglais) qui explique justement la nécessité de ce type de typage pour le multi-dispatching naturel. (il montre avant la faiblesse de celui de Liskov) J'ai toujours eu l'impression que le LSP était lié à ce type de typage, mais je peus méprendre (je n'ai jamais expérimenté les différence de ces deux théories, juste lu un ou deux articles à l'occasion)
    J'ai googlé un peu là-dessus, trouvé quelques références. Personnellement, j'aurais tendance à vouloir appliquer le LSP aussi au sous-typage de Cook aussi. Sinon, je trouve "légères" les affirmations comme quoi Eiffel a un typage de Cook, ou alors Eiffel a beaucoup évolué depuis la dernière fois où je m'y suis intéressé. Smalltalk me paraît un bien meilleur exemple.

    Tes raisonnement avec this se basent aussi sur le langage, en termes de POO tu définis des classes qui peuvent recevoir des messages, que le langage utilise un mécanisme via this importe peu. (pour le moment le seul endroit ou je vois l'influence de this, ou similaire pour d'autre langage, est un article de Deloget ou il le cite pour justifier un point de LOD)
    Que ce soit this importe peu, en revanche, c'est le dispatch qui est important. En terme de contrats, j'ai B::f qui a des préconditions plus restreintes que A::f, ce qui équivaut généralement à une violation du LSP. Mais mon mécanisme de substitution me garantit qu'il n'appellera pas B::f avec un this de type A*. C'est cette garantie qui manque généralement avec les paramètres covariants, qui font qu'ils violent le LSP.

    Pour le principe du raisonnement par l'absurde, je suis d'accord (j'aurais du mal à soutenir que ce n'est pas ca ). C'est aussi ce que koala a fait, il se demande si l'héritage avec changement de visibilité est justifié, et il applique le LSP (violé) et montre que non. Le raisonnement que vous faites montre (c'est un avis personnel ) juste que le C++ n'impose pas le LSP : vous supposez qu'il l'impose, vous faite un programme qui ne le respecte pas et il compile et fonctionne, conclusion le C++ n'impose pas le LSP. Je suis d'accord avec votre raisonnement de manière général mais pas l'hypothèse que vous faites.
    On est donc en désaccord total sur l'interprétation de nos raisonnements respectifs .

    NB: Je comprend parfaitement ton point de vue, si tu remontes les messages tu verras que j'ai été d'accord à un moment (pas mon premier message mais le suivant), mais je trouve 'largumentation de koala vraiment pertinante. (du coup j'ai du mal à trouver d'autre argument que les siens, d'ou une petite inprésion de répétition, aussi pour une argumentation que pour l'autre )
    Oui, j'ai l'impression qu'on tourne un peu en rond parce qu'on a vraiment deux regards différents sur le même problème.

  11. #91
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    J'ai googlé un peu là-dessus, trouvé quelques références. Personnellement, j'aurais tendance à vouloir appliquer le LSP aussi au sous-typage de Cook aussi. Sinon, je trouve "légères" les affirmations comme quoi Eiffel a un typage de Cook, ou alors Eiffel a beaucoup évolué depuis la dernière fois où je m'y suis intéressé. Smalltalk me paraît un bien meilleur exemple.
    Je vais essayer de retrouver une source fiable pour le typage de Eiffel (si Mr Meyers passe sur ce forum qu'il précise ca sera encore plus fiable ) et l'article dont je parlais plus haut en même temps.

    Pour le multi-dipatch, j'ai quand même du mal à voir pourquoi tu parles de ca (à tort surment ), c'est pas lié au LSP. Un cas typique de dipatch c'est une hiérachie (disons A classe abstraite et B C 2 classes filles) et une séries de fonctions (foo(B, B), foo(C, C), foo(B, C), foo(C, B)), 2 objets (a de type A, b de type B) passés sous le type statique A, comment résoudre l'appel foo(a, b) ? Le C++ ne le permet pas naturelement (certains langage le permette), on utilise donc différentes méthodes (visiteur et autre dispatcheur). Je vois pas vraiment la présence du LSP ici (à part entre A B et C). (et que tu introduises ce besoin dans un héritage ne devrait rien changer)

    Pour ton exemple il ne respecte pas le LSP et ne devrait jamais être pratiqué. (ni en C++ ni autre) Ce qui devrait être possible c'est la redéfinition avec des paramètres mère (comme la covariance mais dans l'autre "sens" fille -> mère, je ne sais plus si il y a un terme spécifique).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    struct B;
    struct A { virtual void f(B& b) { };
    struct B :  A { void f(A& a) { }; // certainement pas du c++
    (je ne considère pas ton exemple avec le this, comme je l'ai dit avant je ne voit pas ce qu'il vient faire dans une discussion sur un principe de conception, j'écrit les exemples en C++ car c'est le forum C++, mais un diagramme UML serait bien plus approprié, et je sais pas comment les poster non plus)

    Il me semble que ce sont ces deux "phénomène" (et peut-etre d'autre) que le sous-typage de Cook résoud là où Liskov échoue.

  12. #92
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Voila j'ai trouvé ce que je cherchais (enfin c'était peut-etre pas exactement ce billet mais c'est la même idée :p ) : http://myblog.moquillon.free.fr/inde...rphisme-en-POO (en francais)

    Ce que je retient de ce que souligne l'auteur (et qui est en rapport avec notre sujet) est que le LSP est bien définit en terme de sous-type et non en terme de substitualité. Et qu'une définition en termes de substitualité est possible pour certain langage (duked-type, j'avais cité l'idée de ce genre de langage plus haut), cette idée correspondant à la théorie de Cook sur le typage (@white-tentacle: cf les commentaires du billet pour ce qui est de l'utilisation de ce typage par Eiffel). Mais cette second définition est un autre LSP (qui n'a peut etre pas encore eu de formulation formel publié d'ailleurs), qui ne nous concerne pas !

    <HS>Sinon l'article en soi et celui dont il est tiré (en anglais) sont aussi interessant je trouve </HS>

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    De plus, pour le coup des paramètres co-variants, il faut savoir que cela ne redéfini pas une fonction virtuelle, ca la surcharge

    Mais, ceci dit, il serait surement utile de s'inquiéter de la nécessité de forcer la spécialisation de l'argument ou de l'intérêt d'avoir une fonction (plus ou moins) "type compatible" dans l'interface de base.

    En effet, si la nécessité de spécialiser l'argument passé est bel et bien présente, quid des autres types qui dérivent de la même classe de base, et dont on n'a, a priori, pas besoin d'avoir connaissance lorsque l'on travaille avec une classe donnée

    Je m'explique rapidement:

    Soit une hiérarchie de classes spécialement dédiée aux argument proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class ParamBase
    {
    };
    class ParamDeriv1 : public ParamBase
    {
    };
    class ParamDeriv2 : public ParamBase
    {
    };
    /* class XXX : public ParamBase
        ...
     */
    et une hiérarchie de classe dont une des fonction prend... un des types éventuellement dérivé de ParamBase comme argument 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
    class Base
    {
        public:
            virtual void foo(ParamBase &);
    };
    class Derivee1 : public Base
    {
        /*...*/
    }; 
    class Derivee2 : public Base
    {
        /*...*/
    };
    (si je sépare clairement les deux, c'est pour éviter toute confusion, mais ce pourrait n'être qu'une seule et même hiérarchie )

    Nous pourrions spécialiser Derivee1 ou Derivee2 pour qu'elle manipule des objet ParamDeriv1 (respectivement ParamDeriv2).

    Mais, si, connaissant la classe ParamDeriv1 (resp paramDeriv2), on peut estimer que Derivee1 (resp Derivee2) connait ParamBase, il n'y a, a priori, aucune raison pour que Derivee1 ait connaissance de ParamDeriv2 (respectivement que Derivee2 ait connaissance de ParamDeriv1).

    Or, si on redéfinit foo dans Derivee1 et / ou dans Derivee2 en variant le type d'argument, on tombe effectivement sur le problème que le type réel d'un objet de type ParamBase puisse être... de n'importe quel type héritant de ParamBase, dont une majorité que Derivee1 et Derivee2 n'ont a priori aucun besoin de connaitre l'existence.

    Il y a donc trois solutions possibles, dont le choix dépend des circonstances:

    Soit, on ne déclare pas foo dans Base pour la déclarer (comme prenant directement le type adéquat) non virtuelle uniquement dans les classes dérivées, soit on ne spécialise effectivement pas le type des arguments transmis à foo dans Derivee1 et Derivee2, mais on effectue un test de downcasting dans les redéfinissions de foo afin de s'assurer que le bon type est transmis, en lançant, pourquoi pas, une exception s'il (le downcast) échoue, soit enfin, on considère qu'il est préférable de nommer différemment la fonction manipulant réellement l'objet dérivé dans les classes dérivées.

    Dans le premier cas (pas de déclaration dans Base), il n'y a simplement pas à s'inquiéter de LSP, dans le second, il n'est absolument pas violé.

    Le fait de garder le même nom de fonction en spécialisant les arguments me semble très sérieusement poser un problème de conception, mais pas forcément à cause de LSP (bien que l'on pourrait l'invoquer également)... Pour ma part, c'est plutôt du aux règles qu'imposent C++ en cas de surcharge de fonction, et du phénomène de "shadowing" qu'elles occasionnent.
    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

  14. #94
    Membre Expert
    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
    Par défaut
    Pour le multi-dipatch, j'ai quand même du mal à voir pourquoi tu parles de ca (à tort surment ), c'est pas lié au LSP. Un cas typique de dipatch c'est une hiérachie (disons A classe abstraite et B C 2 classes filles) et une séries de fonctions (foo(B, B), foo(C, C), foo(B, C), foo(C, B)), 2 objets (a de type A, b de type B) passés sous le type statique A, comment résoudre l'appel foo(a, b) ? Le C++ ne le permet pas naturelement (certains langage le permette), on utilise donc différentes méthodes (visiteur et autre dispatcheur). Je vois pas vraiment la présence du LSP ici (à part entre A B et C). (et que tu introduises ce besoin dans un héritage ne devrait rien changer)
    Je vais essayer d'être plus clair, mais ce n'est pas forcément évident . Soit la méthode A::Equals(Like Current). Dans A, elle prend un A en paramètre. Dans B, elle prend un B en paramètre (B dérive de A).

    Si j'écris maintenant le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    A* a = new A();
    B* b = new B();
    A* pbA = b;
    a->Equals(b); // pas de problème, ça appelle A::Equals avec un B
    pbA->Equals(b); // pas de problème, ça appelle B::Equals avec un B
    b->Equals(a); // pas de problème, ne compile pas
    pbA->Equals(a); // compile. En single dispatch, ça appelle B::Equals avec un A, et 
    // ça ça plante lamentablement. En multiple dispatch, ça appelle A::Equals 
    // avec un A (seule fonction qui correspond au type dynamique de tous les 
    // paramètres), et ça ne pose potentiellement pas de problème. Ce n'est pas 
    // suffisant pour garantir le LSP, mais le fait de ne pas l'avoir garantit qu'on 
    // ne le respecte pas.
    Citation Envoyé par Flob90 Voir le message
    Voila j'ai trouvé ce que je cherchais (enfin c'était peut-etre pas exactement ce billet mais c'est la même idée :p ) : http://myblog.moquillon.free.fr/inde...rphisme-en-POO (en francais)

    Ce que je retient de ce que souligne l'auteur (et qui est en rapport avec notre sujet) est que le LSP est bien définit en terme de sous-type et non en terme de substitualité. Et qu'une définition en termes de substitualité est possible pour certain langage (duked-type, j'avais cité l'idée de ce genre de langage plus haut), cette idée correspondant à la théorie de Cook sur le typage (@white-tentacle: cf les commentaires du billet pour ce qui est de l'utilisation de ce typage par Eiffel). Mais cette second définition est un autre LSP (qui n'a peut etre pas encore eu de formulation formel publié d'ailleurs), qui ne nous concerne pas !
    Il est effectivement possible que j'aille largement au-delà de son énoncé initial dans l'interprétation que je fais du LSP .

    Sinon, je continue malgré tout de penser que dire qu'eiffel suit la théorie des types de Cook me paraît léger. J'en veux pour preuve que si une fonction est définie comme prenant un A en paramètre, elle ne prendre un B que si B est un type dérivé de A, peu importe que B respecte l'interface de A. Un meilleur exemple de typage de Cook me paraît être les templates C++ (et notamment, les concepts).

    <HS>Sinon l'article en soi et celui dont il est tiré (en anglais) sont aussi interessant je trouve </HS>
    Oui, merci pour les liens !

  15. #95
    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
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je vais essayer d'être plus clair, mais ce n'est pas forcément évident .
    Open Multi-Methods for C++. Ca n'a pas été retenu pour C++0x mais je pense que ça présente bien ce que tu veux dire (si j'ai bien compris).

  16. #96
    Membre Expert

    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 : 34
    Localisation : France, Doubs (Franche Comté)

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

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Une présentation d'une fonction libre qui a besoin du dispatch est quand même plus simple. (comme dans le lien qu'à donné 3DArchi, et dans le cas du dispatch que j'ai donné). Ce que j'avais pas compris c'était le lien avec le LSP, dans ce que tu présentes il n'est pas vérifié, tu le dis toi même

    Mais ce que tu montres comme exemple est quand même, AMA, un viol du LSP (renforcement des pré-conditions), alors oui comme tu le présentes ca marche, mais les redéfinitions de méthodes c'est pour que ce soit la méthode qui correspond au bon type qui soit appelé, si au final c'est la méthode d'un A qui est appelé (quand tu passes un b et que la méthode est redéfinit), c'est un peu illogique. (pour ca que je préfére une présentation des multi-méthodes hors des classes, c'est bien plus clair AMA)

    Le C++ permet le dispatch sur le type de l'objet (ie this comme tu l'as dit ) mais c'est un peu spécial comme dispatch. Si je t'écrit une fonction normal (enfin plusieurs puisqu'on parle de dispatch) avec un seul paramètre, naturellement le C++ ne le résoudra pas (en effet faire un code qui le permettra est facile en utilisant après le dispatch sur this). Donc le C++ ne supporte pas plus le single-dispatch que les autres, enfin il en supporte un cas particulier mais pas le général.

    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
     
    struct A 
    { virtual ~A() {}  =0; };
    A::~A() {}
    struct B : A { };
    struct C : A { };
    void foo(B&) { /*code*/ } 
    void foo(C&) { /*code*/ }
    //voila un cas de single-dispatch qui n'est pas résolue par le C++ directement
    //en effet le résoudre n'est pas dur
    struct A 
    { 
       virtual ~A() {} 
       virtual void bar() =0;
    };
    void foo(A& a) { a.bar(); }
    struct B; struct C;
    void foo(B&) { /*code*/ } 
    void foo(C&) { /*code*/ }
    struct B : A { void bar() { foo(*this); } };
    struct C : A { void bar() { foo(*this); } };
    //Mais c'est pas pour autant que le C++ le fait tout seul.
    Ce qui serait par contre plus logique c'est qu'on puisse étendre le type des paramtères. (comme dans ton exemple que j'ai récrit dans le message précédent) Et ca le C++ ne le permet pas non plus !

  17. #97
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par Flob90 Voir le message
    C'est donc ceci que je considère comme une ambiguité, le fait que le LSP soit violé du point de vue formel, mais correct là ou il est utile (dans le corps des fonctions qui profite du polymorphisme).
    En fait, c'est tout le noeud (gordien ?) du débat. Le point 1..6 du dernier draft de la norme C++ autorise explicitement ce code.

    Ceci dit, LSP est un principe de Conception Orientée Objet. L'ennonce de Liskov doit être vrai au moment de la conception, pas au moment de la compilation (ou du runtime). Et bien entendu, le fait que le C++ ait une règle qui règle ce point particulier au runtime n'a pas vraiment d'intérêt au niveau design. C'est même totalement décoléré du problème principal. On peut même considéré qu'à ce niveau, C++ est tombé en marche (pour des raisons de simplification du modèle de compilation et du modèle d'exécution).

    Imaginons que le langage HarshAndSlow soit 200 fois plus utilisé que C++, et que ce langage vérifie au runtime la condition (pas si idiote) suivante : une fonction privée d'une instance ne peut être appelée qu'à partir d'une autre fonction membre de la même classe et sur cette même instance. Le runtime du langage fait ça pour des questions de sécurité (par exemple, empêcher l'injection de code).

    Dans ce cas, vous répondriez tous en coeur que "voui, c'est une violation du LSP que de changer le contrôle d'accès d'une fonction virtuelle", et ça vous semblerait évidant.

    Mais que C++ vous le permette (en grande partie à cause de son modèle objet limité), et hop, vous vous interrogez sur le sujet. Je crois que vous vous posez trop de questions

    Notez que je me borne à baser mon raisonnement sur l'énoncé du LSP. Peut être que cet énoncé n'est pas si utile que ça, et que C++ fait les choses telles qu'elles doivent être faites. Mais si la question est "est-ce qu'on viole LSP", alors la réponse est nécessairement oui - malgré tous les arguments (de l'ordre du détail d'implémentation du langage concerné) que vous avancez.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  18. #98
    Membre Expert

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    - influence de la phase où sont faites certaines vérifications (par exemple, le fait que private soit résolu à la compilation et pas à l'exécution est de prime importance ici)
    Ben non

    Citation Envoyé par white_tentacle Voir le message
    LSP est un principe qui ne saurait être appliqué hors contexte, ...
    Ben si

    Citation Envoyé par white_tentacle Voir le message
    Prend le problème à l'envers, essaie de raisonner par l'absurde. Considère que tu VEUX changer la visibilité de la méthode foo dans B par rapport à A (je ne sais pas pourquoi tu veux faire une chose pareille, c'est sûrement une erreur de conception, mais pourquoi pas ). L'application du LSP va te dire que pour toute fonction faisant appel à foo() avec un A, il faut que ça continue de fonctionner si on passe un B à la place du A. Ca tombe plutôt bien, c'est bien le cas en C++...
    Oui, ce qui n'est pas forcément d'un intérêt vérifié dans le cadre de la discussion actuelle

    Comprends moi : si tu as choisi de faire des choses qui violent l'esprit de la conception objet en invoquant le fait que ça ne viole pas la lettre du langage utilisé dans l'implémentation, je vais avoir du mal à te faire changer d'avis (ce qui tombe bien, ce n'est pas ce que je cherche ; je cherche avant tout à provoquer la réflexion, ce qui peut amener un changement d'avis).

    Ceci dit, le langage en question autorise bien d'autres énormités du même calibre - le fait d'autoriser l'utilisation de goto par exemple ; diras-tu que ce n'est pas grave d'utiliser goto dans un programme C++ (et j'ai bien dit C++, pas C) parce que le langage le permet, même si le code écrit au final est une horreur sans nom ?

    Et désolé pour le DP.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  19. #99
    Membre Expert
    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
    Par défaut
    LSP est un principe qui ne saurait être appliqué hors contexte,
    Ben si
    Appliquer hors contexte, je ne sais pas ce que ça veut dire. Je peux énoncer quelque chose, mais pour l'appliquer, j'ai besoin d'un contexte (que je peux mettre dans mon énoncé, c'est vrai).

    Et il n'y a pas UN modèle objet, il y en a plusieurs. La plupart des gens pensent acquis le modèle private/protected/public, il n'existe pas partout (eiffel), il existe aussi avec des variantes (internal en .net, par exemple).

    Et ce dont je suis sûr, c'est que vouloir appliquer le LSP de manière trop stricte conduit à des absurdités (le RTTI dont typeid viole le LSP, par exemple). Quant à vouloir se placer sur un niveau théorique qui n'existe pas (il n'y a pas de conception universelle avec un grand C ou du gras ), ça ne m'intéresse guère... Au final, il y a un contexte que je connais, je conçois en fonction de mon contexte, et j'applique les principes de conception dans mon contexte.

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 633
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Appliquer hors contexte, je ne sais pas ce que ça veut dire. Je peux énoncer quelque chose, mais pour l'appliquer, j'ai besoin d'un contexte (que je peux mettre dans mon énoncé, c'est vrai).
    Mais l'énoncé, c'est justement ce que tu sais au moment où tu commence ton développement, à savoir:
    1. l'analyse des besoins que devra rencontrer ton application
    2. les restrictions propres au langage utilisé (comme l'interdiction de l'héritage publique, par exemple)
    Mais ce n'est pas parce qu'un langage donné permet des choses qui ne sont clairement pas conseillées, voire, des choses que la théorie réprouve que tu dois t'estimer en droit d'adapter la théorie.

    Le fait que C++ permette de se "tirer une balle dans le pied" est quelque chose de reconnu, y compris parmi ses plus fervents adeptes.

    La conclusion que l'on en tire est qu'il est primordial d'avoir une conception et une algorithmie absolument sans faille. Et cela aussi est un fait unanimement reconnu.

    Mais, comment peux tu espérer atteindre ce "haut niveau" de qualité dans la conception si, à la base, tu te permet d'adapter une règle théorique sensée être appliquée à tous les langages, sous prétexte que "C++ n'interdit pas de ne pas la respecter"

    A partir du moment où un langage, quel qu'il soit, autorise un processus qui peut s'apparenter (sous différentes variantes) à l'héritage et au polymoprhisme, tu n'as pas le choix: tu dois appliquer LSP, et tu dois l'appliquer de manière identique quel que soit le langage.

    La seule concession que tu puisse faire, c'est que si le langage permet moins que ce que LSP autorise, c'est de te limiter à ce qui est autorisé par le langage (le meilleur exemple étant l'héritage multiple).
    Et il n'y a pas UN modèle objet, il y en a plusieurs. La plupart des gens pensent acquis le modèle private/protected/public, il n'existe pas partout (eiffel), il existe aussi avec des variantes (internal en .net, par exemple).
    Quelle que soient les termes utilisés pour identifier la dualité "accessible depuis l'extérieur ou non", elle reste une des bases du modèle objet: c'est parce que l'on a la possibilité de rendre quelque chose inaccessible depuis l'extérieur que l'on peut s'abstraire des données réellement manipulées pour s'intéresser en priorité aux service que les objets doivent être capables de rendre.

    Et sur ce point, LSP est parfaitement clair: si la propriété "peut rendre tel service" d'un objet du type de base est valide, elle doit le rester pour l'objet de type dérivé.

    Et ce dont je suis sûr, c'est que vouloir appliquer le LSP de manière trop stricte conduit à des absurdités (le RTTI dont typeid viole le LSP, par exemple).
    En quoi RTTI viole-t-il LSP

    Le but de RTTI est uniquement de nous permettre de récupérer de manière certaine le type réel d'un objet de type dérivé au départ d'un objet "qui passe pour être" du type de base lorsqu'on a été "assez bête" pour croire que le fait d'utiliser le type parent suffirait.
    Quant à vouloir se placer sur un niveau théorique qui n'existe pas (il n'y a pas de conception universelle avec un grand C ou du gras ), ça ne m'intéresse guère... Au final, il y a un contexte que je connais, je conçois en fonction de mon contexte, et j'applique les principes de conception dans mon contexte.
    Soit, mais, au moins, veille à ne prendre que le contexte qui va bien...

    Et puis, malgré tout, il ne faut pas oublier que tout dans la programmation n'est jamais qu'une mise en pratique de théories données, donc, tu dois les respecter scrupuleusement si tu veux obtenir des résultats cohérents

    Encore une fois, si tu décide d'adapter la théorie sous prétexte que "le langage l'autorise", tu ne devra pas tellement t'étonner si, au final, tu obtiens des résultats incohérents
    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