IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Sémantique de valeur et héritage


Sujet :

C++

  1. #1
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut Sémantique de valeur et héritage
    Hello,

    Bon, je remets sur le tapis un sujet récurrent, mais qui garde toujours quelques traces de nébulosités.

    Dans la FAQ, il est dit que cela a peu de sens d'avoir de l'héritage avec une sémantique de valeur (je ne vois d'ailleurs pas pourquoi la FAQ s'intéresse particulièrement aux fonctions virtuelles). Je ne comprend pas vraiment pourquoi.

    En l'occurrence, je travaille sur une classe qui représente un coût. Ce coût comprend différentes valeurs scalaires, de type différent, qui se complètent. J'ai une autre classe de coût qui ajoute des composantes scalaires, et qui hérite donc de la première classe.

    Pourquoi cela pose-t-il un problème ?

  2. #2
    Inactif  


    Homme Profil pro
    Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Inscrit en
    Décembre 2011
    Messages
    9 012
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France, Loire (Rhône Alpes)

    Informations professionnelles :
    Activité : Doctorant sécurité informatique — Diplômé master Droit/Économie/Gestion
    Secteur : Enseignement

    Informations forums :
    Inscription : Décembre 2011
    Messages : 9 012
    Points : 23 145
    Points
    23 145
    Par défaut
    Bonjour,

    Alors je ne me souviens plus vraiment de la démonstration.

    Les classes à sémantique d'entité doivent interdire la copie et l'affectation. En revanche, on peut implémenter une méthode clone() par exemple.

    Les classes à sémantique de valeurs doivent autoriser la copie et l'affectation.

    Or, si ont a une classe à sémantique de valeur avec un héritage (ex. Fille hérite de Mère), on peut avoir une fonction qui prenne en paramètre une Mère :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void foo(Mere & mere)
    {
    }
    On pourra alors lui transmettre une Fille grâce au polymorphisme. Or si cette fonction foo se met à faire une copie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo(Mere & mere)
    {
            Mere m = mere;
    }
    On va se retrouver avec un objet qui n'est pas une Fille, ainsi on risque d'avoir :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    Mere m = mere;
    m != mere;
    Sans compter que l'objet m peut très bien être dans un état "invalide" pour une instance de Mere.

    Ainsi il est très déconseillé d'utiliser l'héritage publique pour des classes à sémantique de valeur.

  3. #3
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Il y a deux soucis :
    - un sémantique -> Quel est le sens dans ton système à ajouter des couts qui ne sont pas du même type (ou même de les comparer, car c'est la même question) ? Il me semble que tu penses factorisation de code, et non pas LSP. Or un héritage (public) sans LSP, c'est un héritage bancal. Autant faire de la composition.
    P.ex., si je dois écrire Point + Vector, je ne vais pas dériver publiquement Point3D de Point2D pour écrire le bousin. A la place, je veux un meilleur typage qui ne me laissera pas faire des non-sens comme Point3D + Vector2D.

    - un technique -> le slicing rend très difficile l'écriture ICout operator+(ICout, ICout) (il faut passer par l'idiome enveloppe-lettre ou d'autres feintes dans le genre)

    Bref, de la lecture:
    - http://www.drdobbs.com/cpp/compariso...ance/240149250
    - http://www.drdobbs.com/cpp/if-c-obje...equa/240146950
    - http://www.angelikalanger.com/Articl...ls/Equals.html
    - http://www.developpez.net/forums/d13...n/#post7141642 (je m'étais emmêlé dans mes ensembles ^^')
    - http://akrzemi1.wordpress.com/2012/0...lue-semantics/
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  4. #4
    Membre éprouvé
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2009
    Messages
    552
    Détails du profil
    Informations personnelles :
    Localisation : France

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

    Informations forums :
    Inscription : Mars 2009
    Messages : 552
    Points : 1 060
    Points
    1 060
    Par défaut
    Citation Envoyé par oodini Voir le message
    En l'occurrence, je travaille sur une classe qui représente un coût. Ce coût comprend différentes valeurs scalaires, de type différent, qui se complètent. J'ai une autre classe de coût qui ajoute des composantes scalaires, et qui hérite donc de la première classe.
    Le problème survient a mon sens quand on se retrouve à faire des copies qui change le typage :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Cout cout = unCoutDerive;
    Ça peut être gênant au sens où ça dénature ton coût, mais il me semble que c'est pas toujours catastrophique :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    // projection renvoyant un PointSurPolyligne, dérivé de point
    Point point = projection( polyligne, point ) ;

  5. #5
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Et justement, qui dit sémantique de valeur, dit copie. Et avec l'héritage, cela dit slicing.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  6. #6
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Vos réponse m'éclairent pas mal, mais admettons que j'ai des classes pour représenter le cout d'un salarié pour une entreprise.
    Disons que la classe de base, pour les employés standard, appelée CoûtSalarié, comporte comme données le salaire et les tickets restaurants.
    Il ne me paraît pas délirant d'avoir une classe dérivée CoûtSalariéPrimé, pour les commerciaux par exemple, qui auraient en plus une données concernant le montant des primes.

    Le cout d'un salarié sujet aux primes est bien une spécialisation d'un salarié. Sémantiquement, l'argument qui m'a été présenté ne me semble pas toujours valide.

    Seul l'argument technique reste pleinement valide. Mais il faudrait peut-être expliciter cela dans la FAQ (ou du moins ne pas présenter cela comme une évidence).

    L'héritage privé me semble être un bon compromis.

  7. #7
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Pour moi, il y a une erreur de modélisation. Le cout d'un salarié est une table associative (aka dictionnaire dans les langages de scripts à la mode). Cela permet de rajouter facilement de nouveaux champs.
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  8. #8
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Citation Envoyé par oodini Voir le message
    Vos réponse m'éclairent pas mal, mais admettons que j'ai des classes pour représenter le cout d'un salarié pour une entreprise.
    Disons que la classe de base, pour les employés standard, appelée CoûtSalarié, comporte comme données le salaire et les tickets restaurants.
    Il ne me paraît pas délirant d'avoir une classe dérivée CoûtSalariéPrimé, pour les commerciaux par exemple, qui auraient en plus une données concernant le montant des primes.

    Le cout d'un salarié sujet aux primes est bien une spécialisation d'un salarié. Sémantiquement, l'argument qui m'a été présenté ne me semble pas toujours valide.

    Seul l'argument technique reste pleinement valide. Mais il faudrait peut-être expliciter cela dans la FAQ (ou du moins ne pas présenter cela comme une évidence).

    L'héritage privé me semble être un bon compromis.
    Tu confonds la valeur du cout et le system qui calcul cette valeur. Les deux devraient etre dissocies. Le cout devrait avoir semantique de valeur (peut etre differents types pour differents types de couts, composes via heritage prive et "using", ou encore via CRTP) tandis que le systeme qui calcul le dis coup pourrait faire partie d'une hierarchie ou il y aurait specialization.

    Tout mettre dans la meme classe c'est pas du tout une bonne idee.

  9. #9
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Je n'ai nulle part parlé de système de calcul des coûts.

  10. #10
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Il y a deux soucis :
    - un sémantique -> Quel est le sens dans ton système à ajouter des couts qui ne sont pas du même type (ou même de les comparer, car c'est la même question) ? Il me semble que tu penses factorisation de code, et non pas LSP. Or un héritage (public) sans LSP, c'est un héritage bancal. Autant faire de la composition.
    P.ex., si je dois écrire Point + Vector, je ne vais pas dériver publiquement Point3D de Point2D pour écrire le bousin. A la place, je veux un meilleur typage qui ne me laissera pas faire des non-sens comme Point3D + Vector2D.

    [...]

    - http://www.angelikalanger.com/Articl...ls/Equals.html
    Ce qui est amusant, c'est que tu parles de LSP, issu du cerveau de Barbara Liskov, qui se sert justement d'une hérarchie Point2D/Point3D pour illustrer l'implémentation en Java de la méthode equals() (sémantique de valeur).
    Pour juste se faire dégommer par Angelika Langer qui dit que son implémentation est pourrie (non respect de la transitivité).

    Comme quoi, ce n'est pas si évident !

  11. #11
    Expert éminent sénior
    Avatar de Luc Hermitte
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2003
    Messages
    5 275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Août 2003
    Messages : 5 275
    Points : 10 985
    Points
    10 985
    Par défaut
    Pour moi, un point3D n'est pas un point2D, et que si on utilise l'héritage pour rajouter des choses à un type, ça ne marche pas comme ça. Mais alors pas du tout! [Et malheureusement, c'était l'argument de vente de l'héritage des années 80-90 -> réutiliser du code... ]
    Là, l'exemple auquel tu fais référence vient du monde Java, et Java ne permet pas de proposer un héritage de réutilisation qui ne permet pas une substituabilité syntaxique -- note dans les conclusions le "ne pas hériter aurait été plus propre ici". De plus, le Java souffre, comme le C++, du non support des multi-méthodes.

    Et là, nous sommes à la croisée de tous ces problèmes à vouloir implémenter l'égalité sur des objets tirés de hiérarchies juste pour pouvoir factoriser du code source par héritage.
    Le problème en C++ ne se voit pas sur l'égalité, mais au moment de vouloir copier. Et les racines (sémantiques, j'ai envie de dire) du problème sont les mêmes, bien que les conséquences techniques soient différentes.
    Ce n'est pas une théorie, mais une intuition personnelle (comme quoi, c'est le même problème)
    Blog|FAQ C++|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS|Bons livres sur le C++
    Les MP ne sont pas une hotline. Je ne réponds à aucune question technique par le biais de ce média. Et de toutes façons, ma BAL sur dvpz est pleine...

  12. #12
    Membre expert
    Avatar de Klaim
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Août 2004
    Messages
    1 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 717
    Points : 3 344
    Points
    3 344
    Par défaut
    Citation Envoyé par oodini Voir le message
    Je n'ai nulle part parlé de système de calcul des coûts.
    A ce que je sache, il y a un calcul pour obtenir les differents couts non? A chaque fois que tu derive un cout d'une autre, il y a un calcul. Ce n'est donc apparement pas une histoire d'heritage de concept mais de calculs. Du moins de ce que je comprends de ce que tu decris.

  13. #13
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Dans mon cas, ce sont des données désérialisées.

    Cela dit, j'avais évacué un peu vite ton intervention.

    Tu disais qu'il fallait séparer les données des fonctions sur ces données. Le principe des classes n'est-il pas de les regrouper ?

  14. #14
    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
    Salut,
    Citation Envoyé par oodini Voir le message
    Dans mon cas, ce sont des données désérialisées.

    Cela dit, j'avais évacué un peu vite ton intervention.

    Tu disais qu'il fallait séparer les données des fonctions sur ces données. Le principe des classes n'est-il pas de les regrouper ?
    Non, le principe des classes est de fournir un ensemble cohérent de services, indépendamment de la manière don les données sont représentées.

    Dans certains cas, les services attendus ne font "que" donner accès à la donnée sous-jacente, mais dans le cas de couts, tu dois envisager chaque type susceptible de représenter un cout comme une monnaie différente: Si tu vas aux états unis, tu aura besoin de dollars US, et tu auras besoin de dollars Canadien si tu vas juste un peu sur le coté.

    Dans les deux cas, il s'agit de dollars, mais la valeur d'un dollar (je dirais presque la valeur "or" d'un dollar, si ce n'est qu'il n'est plus lié à l'or depuis les années 80, il me semble) est différente, ce qui fait que tu devra convertir le montant en l'un pour savoir combien payer en l'autre, idem pour l'euro ou pour les autres monnaies.

    Toutes les monnaies ont un but identique: te permettre de monnayer des biens et des services, mais chaque monnaie a une valeur qui lui est propre et qui fait qu'un bien ou un service donné te coutera un montant différent en fonction de la monnaie envisagée.

    Dans le cas présent, il n'y a pas plus de sens à mélanger les couts qu'à demander à être payé pour un tiers en euro, un tiers en dollars US et un tiers en dollars canadien, ou qu'il n'y en a à essayer d'introduire un point 3D alors que tout ton référentiel est en 2D seulement.

    Tu peux envisager une conversion pour ce qui est convertible (utiliser un taux de conversion pour avoir une somme correspondante soit en euro, soit en USD soit en DCan, envisager une projection d'un point 3D dans ton référentiel 2D, ... ) .

    Mais l'héritage va essentiellement avoir comme résultat de te permettre de faire passer l'un (la classe dérivée) pour l'autre (la classe de base), avec comme résultat, la possibilité de créer une collection d'objets dans laquelle tu pourrait mettre aussi bien l'un que l'autre, étant donné que tu peux faire passer l'un pour l'autre.

    Cette approche n'est clairement pas adaptée aux classes ayant sémantique de valeur: pour éviter les problèmes (de slicing et autres), tu convertis d'abord ta donnée dans un type unique, et tu la rajoutes dans une collection susceptible de contenir ce genre de valeur uniquement
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  15. #15
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Décembre 2010
    Messages
    734
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Décembre 2010
    Messages : 734
    Points : 1 475
    Points
    1 475
    Par défaut
    Pour ce qui est du coût je dirais effectivement comme Klaim que fixe vs à prime n'est pas le coût lui même (qui est une valeur scalaire) mais bien le barème qui pour sa part aurait plutôt une sémantique d'entité puisque il relève du contrat de travail d'un employé (même avec exactement avec les mêmes paramètres deux contrats de travail sont distincts)

  16. #16
    Membre émérite
    Profil pro
    Inscrit en
    Novembre 2004
    Messages
    2 764
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2004
    Messages : 2 764
    Points : 2 705
    Points
    2 705
    Par défaut
    Bon, je vais clore ce sujet en écartant l'héritage, mais plus convaincu pour des raisons d'implémentation (slicing) que pour des raisons sémantiques.

    Merci à tous !

    PS : koala01, je désespérais de lire ton intervention sur un sujet qui t'est si cher.

  17. #17
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Hum il me semble que tu avais déjà lancé ce débat dans un autre thread une fois

    Au delà du problème technique que pose la combinaison d'une sémantique de valeur avec de l'héritage, cela pose un problème pragmatique : c'est chiant comme la mort à implémenter, et ça fait du code pas très souple, notamment lorsqu'on veut enlever/rajouter des classes. De plus, ça rend l'usage des collections de la STL plus délicat et moins pratique.

    Un bon exemple de problème dans lequel on pourrait se retrouver à faire ça, c'est lorsqu'on veut représenter une expression (mathématique). Tu pourrais avoir une classe de base Expression avec les différentes entités binaires et unaires qui en héritent (NombreEntier, Addition, Multiplication) par exemple. Lorsque je fais face à une telle situation, je préfère tout mettre dans une seule classe capable de représenter chacun de ces types (car en général, leur nombre est assez réduit). C'est un peu moins beau niveau design (seulement un peu) mais c'est bien plus simple à coder, surtout quand tu commence à coller ça dans des collections. Si le nombre de types différents devient grand ou est appelé à évoluer, je me tournerais vers un autre design (avec des templates par exemple).

    Pour ton problème précis oodini, je n'utiliserais pas de l'héritage mais de la composition. Je prend un exemple ou la composition se révèle plus pratique que l'héritage pour ton type de problème: mettons que tu aies un employé qui n'est pas primé mais qui va le devenir au cours de l'exécution. Avec ton choix d'héritage, tu vas être obligé de remplacer ton instance de CoutEmploye par une nouvelle instance de CoutEmployePrime. Si cette instance est référencée par d'autres objets, il faut tout mettre à jour. Si tu as plusieurs primes touchées de différentes manières, tu vas devoir écrire une classe par combinaison de primes possible ! A partir de 3 primes ou plus, ça devient un enfer.
    Tu devrais plutôt n'avoir qu'une seule classe Employe qui aggrège plusieurs coûts (Salaire, Prime1, Prime2, ...), avec une sémantique d'entité pour les employés et éventuellement de valeur pour les coûts.
    Find me on github

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

Discussions similaires

  1. Réponses: 8
    Dernier message: 13/10/2008, 20h26
  2. connaitre la valeur sémantique d'un string
    Par vanhel dans le forum Langage
    Réponses: 4
    Dernier message: 20/05/2008, 14h32
  3. Sémantique de valeur et bosst::shared_ptr
    Par bolhrak dans le forum Boost
    Réponses: 1
    Dernier message: 11/09/2007, 00h35
  4. [VB.NET]Héritage : valeur d'une propriété perdue
    Par denilson dans le forum Windows Forms
    Réponses: 1
    Dernier message: 06/07/2006, 11h50

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