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 :

Héritage public et copie/déplacement/échange


Sujet :

Langage C++

  1. #1
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut Héritage public et copie/déplacement/échange
    Bonjour.

    Ce sujet fait suite à un échange lancé par cette question, qui n'avait plus rien à voir avec le sujet d'origine...
    Je crée donc un topic séparé.

    Donc, on dispose d'une hiérarchie de classes en héritage public.
    Elles ont très certainement une sémantique d'entité, et donc ne sont ni copiables (ne possèdent pas de constructeur par copie (accessible)) ni assignable (ne possèdent pas d'opérateur d'affectation (par copie) (accessible)).

    Je ne voyais pas vraiment pourquoi jusqu'à cette remarque, qui pointe un problème de cohérence.
    Sans doute parce que je considérais qu'une classe doit être pleinement responsable des attributs membre qu'elle définit.
    Peut-être une vision un peu trop stricte...
    Bref.


    Si l'on veut tout de même copier un objet d'une telle hiérarchie, on peut utiliser le design pattern « clone », dixit la F.A.Q..
    Mais cela implique que les constructeurs par copie existent, bien que protégés ou privés.
    Dans ce cas, à quoi faut-il faire attention lors de l'écriture de ces constructeurs, pour être sûr de rester correct et cohérent ?
    Est-ce que les constructeurs par copie « par défaut » peuvent convenir ?

    Maintenant que l'on a vu la copie, la question de l'assignation se pose.
    Est-ce incorrect de se dire : « Je ne veux plus que cette variable représente cet objet, dorénavant elle représentera cet autre objet. » ?
    Si la variable en question est un pointeur, un delete suivi d'une affectation au résultat de clone() sur l'objet adéquat fait l'affaire.
    Mais sinon ?
    Peut-on envisager une fonction membre clone() qui prendrait en paramètre l'adresse à laquelle dupliquer l'objet, ou une référence sur l'objet cible, et qui utiliserait l'opérateur d'affectation ?
    Si oui, même question que précédemment.


    Voyons à présent le déplacement.
    Il n'y a pas de raison qu'une classe non copiable ne supporte pas le déplacement, n'est-ce pas ?
    J'imagine que l'on pourrait se contenter du constructeur par déplacement et de l'opérateur d'affection par déplacement par défaut, mais on risque de se retrouver confronté au problème de cohérence indiqué par la remarque citée plus haut.
    Alors comment faut-il faire ?


    Passons maintenant à l'échange.
    Ce n'est pas parce qu'une classe n'est pas copiable qu'elle n'est pas échangeable, si ?
    L'implémentation « par défaut » de l'échange passe par le constructeur par copie et l'opérateur d'affectation.
    Dommage...
    Pour le C++11, c'est le constructeur par déplacement et l'opérateur d'affectation par déplacement.
    Dommage également...

    Du coup, je me pose les mêmes question que précédemment...
    Surtout qu'il y a deux cas à gérer : si on utilise l'idiome « copy and swap » pour l'opérateur d'affectation ou non...


    Voilà.
    J'espère que vous pourrez m'aider à y voir un peu plus clair.

  2. #2
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    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
    Points : 4 551
    Points
    4 551
    Par défaut
    Tout ce qui suit n'est que mon opinion, et j'espère que la discussion qui va suivre va s'avérer tout aussi intéressante que la question.

    Situons nous, histoire d'essayer d'y voir plus clair, dans une hiérarchie de classes A->B->C qui ont une sémantique de valeur - elles sont donc copiables et assignables (-> signifie "est la classe mère de").

    Construction par copie

    Dans ce cas, la question est de savoir si les relations suivantes ont du sens :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    /* - */ A a1;
    /* 1 */ B b1(a1);
    /* 2 */ C c1(b1);
    /* 3 */ C c2(a1);
    /* - */ C c3;
    /* - */ C c4;
    /* - */ B b2;
    /* 4 */ B b3(c3);
    /* 5 */ A a2(c4);
    /* 6 */ A a3(b2);
    S'il y a relation d'héritage, alors le principe de substitution est censé être respecté : c1 et c2 se comportent comme une instance de A vis à vis des clients de A (pour le dire autrement, tout utilisateur d'une instance de A peut utiliser à la place une instance de C sans se rendre compte du fait qu'il utilise une instance de C). De la fonction :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    A *clone(A* instance)
    {
      return new A(*instance);
    }
    Il ressort que les relations 4, 5 et 6 ci-dessus sont parfaitement valides - et même nécessaire, si on souhaite que le principe de substitution de Liskov soit respecté. Le constructeur par copie de A se comporte à son tour comme un client de A vis à vis du LSP, donc il faut que le comportement des instances de type B et C ne diffèrent pas du comportement d'une instance de A dans ce cadre.

    Quid des relations 1, 2, et 3 ? 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
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
     
    #include <iostream>
     
    #define PRINT do { std::cout << __PRETTY_FUNCTION__ << std::endl; } while (0)
     
    class A
    {
    public:
    	A() { PRINT; }
    	A(const A&) { PRINT; }
    	virtual ~A() { }
    	A& operator=(const A&) { PRINT; return *this; }
    };
     
    class B : public A
    {
    public:
    	B() { PRINT; }
    	~B() { }
    };
     
    int main()
    {
    	A a1;
    	A a2(a1);
    	B b1;
    	B b2(b1);
    	B b3(a2);
    }
    Ne compile pas :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    g++     cctor.cpp   -o cctor
    cctor.cpp: In function ‘int main()’:
    cctor.cpp:27:9: error: no matching function for call to ‘B::B(A&)’
    cctor.cpp:27:9: note: candidates are:
    cctor.cpp:17:2: note: B::B()
    cctor.cpp:17:2: note:   candidate expects 0 arguments, 1 provided
    cctor.cpp:14:7: note: B::B(const B&)
    cctor.cpp:14:7: note:   no known conversion for argument 1 from ‘A’ to ‘const B&’
    Il y a une bonne raison à ça : le constructeur par copie implicite et public qui est créé pour une class B par le compilateur de manière automatique a pour signature B::B(const B&), et la conversion A& vers B& est impossible (même si A& est en fait une référence vers une instance de B ; dans ce cas, il faut un dynamic_cast<> pour récupérer l'instance de B).

    A noter que supprimer la déclaration explicite du constructeur par copie de A ne change rien à l'affaire : le constructeur par copie implicite de B continuera de prendre en paramètre un const B&.

    Donc la réponse à la question revient à se demander si c'est une bonne chose de créer un constructeur de B qui prendrait en paramètre une référence constante sur une instance de A - en gros, créer de manière explicite un constructeur B::B(const A&).

    Une telle approche peut être tout a fait légitime dans certains cas (on peut l'imaginer assez aisément lors d'une utilisation du CRTP). En fait, elle est valide dès lors que B modifie le comportement de la classe A dont elle hérite sans changer ses propriétés intrinsèques (B est un A avec des algorithmes différents). Il ne faut pas se leurrer : de tels cas sont peu courants. Là où on se rends compte qu'une telle approche n'est pas la bienvenue, c'est lorsqu'on a une troisième classe C qui hérite de B : dès lors que C doit contenir son constructeur par copie explicite, ainsi que un constructeur par classe parent, alors il est plus que probable qu'il y a un problème de respect du LSP ou de OCP.

    Assignation

    Au niveau de l'assignation, on a strictement le même raisonnement, avec exactement les même limites.

    Déplacement

    On amende le code ci-dessus pour obtenir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    #include <iostream>
     
    #define PRINT do { std::cout << __PRETTY_FUNCTION__ << std::endl; } while (0)
     
    class A
    {
    public:
    	A() { PRINT; }
    	A(const A&) { PRINT; }
    	A(A&&) { PRINT; }
    	virtual ~A() { }
    	A& operator=(const A&) { PRINT; return *this; }
    };
     
    class B : public A
    {
    public:
    	B() { PRINT; }
    	~B() { }
    };
     
    int main()
    {
    	A a3(std::move(B()));
    }
    Le compilateur permet bien évidemment d'écrire ce code - il est tout a fait valide de déplacer une instance de B dans une instance de A, parce que une instance de B est une instance de A.

    Un problème survient quand même, car on perds les comportements spécifique de l'instance de B en faisant ça. Ca n'est pas particulièrement choquant, mais on doit le garder en mémoire lorsqu'on effectue cette opération très particulière.

    Maintenant, déplacer une instance de A dans une instance de B a-t-il du sens ? Instinctivement, je dirais non, parce qu'il semblerait que ce déplacement n'en soit plus un, car il s'agirait là d'une transformation.

    Mais c'est sans compter le raisonnement fait ci-dessus : si construire une instance de B à partir d'une instance de A a du sens, alors le déplacement d'une instance de A dans une instance de B a tout autant de sens.

    Echange
    L'échange étant défini par l'autorisation (ou non) de l'affectation, dès lors que les opérateurs d'affectation sont correctement définis alors l'échange est correctement défini. Ceci dit, le prototype canonique de la fonction swap est (pour simplifier ; ce n'est pas tout a fait exact (c'est même beaucoup plus compliqué en fait)):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    namespace std
    {
        template <class _Type>
            void swap(_Type& instance1, _Type& instance2);
    }
    Et non pas
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    namespace std
    {
        template <class _Type1, class _Type2>
            void swap(_Type1& instance1, _Type2& instance2);
    }
    Qui serait nécessaire pour implémenter l'échange entre une instance de A et une instance de B (même si B hérite de A et que tous les opérateurs sont là). Les concepts auraient probablement permis de se passer de cette limitation, mais les concepts ont été abandonnés en route.

    Conclusion

    Dans la hiérarchie A->B, les relations suivantes sont trivialement acceptables :
    • copier un B dans une nouvelle instance de A
    • assigner un B dans une instance de A
    • déplacer un B dans une instance de A

    Les relations suivantes ne sont acceptables que dans le cadre d'une architecture particulière, avec condition
    • copier un A dans une nouvelle instance de B
    • assigner un A dans une instance de B
    • déplacer un A dans une instance de B

    Enfin, la relation suivante pose des problèmes d'implémentation, mais est acceptable aux mêmes conditions que les 3 relations précédentes:
    • échanger une instance de A et une instance de B
    [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.

  3. #3
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Tout ce qui suit n'est que mon opinion, et j'espère que la discussion qui va suivre va s'avérer tout aussi intéressante que la question.
    C'est bien pour cela que j'ai créé cette discussion...


    Citation Envoyé par Emmanuel Deloget Voir le message
    Dans la hiérarchie A->B, les relations suivantes sont trivialement acceptables :
    • copier un B dans une nouvelle instance de A
    • assigner un B dans une instance de A
    • déplacer un B dans une instance de A
    C'est exactement ce que je pensais jusqu'à la remarque de jblecanard (citée dans mon premier message).
    Mais qu'entends-tu exactement par « trivialement acceptables » ?
    Qu'elles sont correctes syntaxiquement et sémantiquement, mais que ce n'est pas forcément une bonne idée de les utiliser ?


    Citation Envoyé par Emmanuel Deloget Voir le message
    Les relations suivantes ne sont acceptables que dans le cadre d'une architecture particulière, avec condition
    • copier un A dans une nouvelle instance de B
    • assigner un A dans une instance de B
    • déplacer un A dans une instance de B
    Je dois dire que cela ne m'avait jamais traversé l'esprit.
    Elles me paraissent tellement absurdes...


    Citation Envoyé par Emmanuel Deloget Voir le message
    Enfin, la relation suivante pose des problèmes d'implémentation, mais est acceptable aux mêmes conditions que les 3 relations précédentes:
    • échanger une instance de A et une instance de B
    Pour ce cas-là, il faudra transtyper explicitement l'instance de B en une instance de A, puisque comme tu le fais justement remarquer, la fonction swap() par défaut n'accepte que deux arguments du même type.
    Et dans ce cas, le programmeur ne peut pas échanger une instance de A et une instance de B sans s'en rendre compte ; il doit savoir ce qu'il fait.
    Par contre, si la fonction swap() a été redéfinie pour le type A, là ça peut poser problème.

    Je dois dire que là aussi, je n'ai jamais pensé à échanger deux instances de types différents mais liés hiérarchiquement.
    Quoiqu'il suffit de travailler avec des références ou des pointeurs sur les types de bases pour masquer cet état de fait.
    Du coup, je suppose que ça annule ma première remarque...


    Au final, quel est ton point de vue Emmanuel ?
    Que l'on peut utiliser une hiérarchie de classes à sémantique de valeur, à condition de prendre des précautions, ou qu'on ne peut pas (devrait pas ?) le faire, car cela viole le principe de substitution de Liskov ?

  4. #4
    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 Steph_ng8 Voir le message
    C'est exactement ce que je pensais jusqu'à la remarque de jblecanard (citée dans mon premier message).
    Mais qu'entends-tu exactement par « trivialement acceptables » ?
    Qu'elles sont correctes syntaxiquement et sémantiquement, mais que ce n'est pas forcément une bonne idée de les utiliser ?
    Emmanuel veut simplement dire que l'on peut accepter l'idée de ces opérations sans se poser de question (le terme trivial indique que c'est quelque chose qui peut survenir de manière habituelle )

    En effet, étant donné que tout B est de toutes manières un A, copier, assigner ou déplacer un B vers un A se fera sans se poser de question, tout en gardant en mémoire que certaines informations sont perdues: on ne fait que prendre un sous ensemble de ce qui compose le B
    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. #5
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Ok, merci.
    C'est ce que je me disais, mais je voulais m'en assurer.

    Finalement, cela me conforte dans l'idée que l'on peut (du moins en théorie) avoir une hiérarchie de classes avec héritage public ayant une sémantique de valeur.
    (Ou alors, c'est que je n'ai pas compris là où il voulait en venir... )
    Ça n'a pas l'air de te choquer plus que ça, koala01...
    Je me fais des idées ?

  6. #6
    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
    Le fait est que je suis d'accord avec l'analyse théoriqued'Emmanuel, mais que je ne vois absolument pas dans quelle circonstance nous pourrions avoir une hiérarchie de classe ayant sémantique de valeur!

    Ma réaction est donc proche du
    En théorie, cela devrait pouvoir se faire sous condition, mais, en pratique, j'attends encore que l'on me présente un exemple concret de hiérarchie de classses ayant sémantique de valeur
    Je ne dis pas qu'il est impossible d'apporter un tel exemple, je dis juste que ce genre d'exemple doit se compter sur les doigts d'une main en regroupant l'ensemble des projets existant

    J'ai d'autant plus de mal à imaginer une telle hiérarchie que l'analyse d'Emmanuel montre qu'il y a quand même un grand nombre d'écueils à éviter.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Je vois.

    Bon, l'analyse d'Emmanuel a beau être pertinente (et je le remercie de l'avoir faite ), cela ne répond pas à ma question de départ.
    À partir d'une hiérarchie de classes à héritage public et sémantique d'entité, komankonfé pour mettre en place un « système » de copie et/ou déplacement et/ou échange correct ?

  8. #8
    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
    Deux solutions:
    - idiome enveloppe-lettre
    - mini framework d'objets réguliers
    lien anti-radotage

    Reste que c'est généralement mal, et qu'après analyse/expérience, on se rend compte que l'on n'a jamais eu besoin de dupliquer des objets tirés d'une hiérarchie polymorphe.
    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...

  9. #9
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    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
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par Koala01
    je ne vois absolument pas dans quelle circonstance nous pourrions avoir une hiérarchie de classe ayant sémantique de valeur!
    Je me demandais quand on allais me le dire ! Je devrais vous répondre, mais Prattchet a dis "l'océan se moque de quel coté nage les poissons", ce qui ne veut rien dire, mais que je trouve approprié.

    En fait, je n'ai pas de réponse à cette question. Plus exactement, je n'ai pas de bonne réponse (je suis sûr que j'en ai des mauvaises).

    Citation Envoyé par Steph_ng8 Voir le message
    Je vois.

    Bon, l'analyse d'Emmanuel a beau être pertinente (et je le remercie de l'avoir faite ), cela ne répond pas à ma question de départ.
    À partir d'une hiérarchie de classes à héritage public et sémantique d'entité, komankonfé pour mettre en place un « système » de copie et/ou déplacement et/ou échange correct ?
    Merci pour les compliments, mais la question se pose de manière très différente pour les classes avec une sémantique d'entité. Autant une valeur peut être "tronquée", autant une entité, si elle est modifiée par une copie/un déplacement, perd complètement sa sémantique. Donc on ne permet pas la copie d'entités autrement que par un clonage. En fait, les deux types de création à partir d'une entité sont :

    * le clonage. Pas besoin d'explication pour comprendre

    * la transformation : l'entité se transforme en une autre entité, par exemple parce que son état a trop changé pour être considéré comme étant l'entité qu'elle était avant. Ex: l'entité joueur_de_foot_pro est créée à partir de l'entité joueur_de_foot_amateur ou à partir de l'entité joueur_de_foot_en_formation.

    On ne peux pas échanger des entités, donc la question ne se pose pas. De même, on ne peut pas déplacer une entité vers un autre type d'entité sans lui faire perdre sa sémantique, donc la question ne se pose pas non plus. Une entité a deux états : elle existe ou elle est détruite. A partir d'une entité existante, on peut créer une entité exactement similaire, ou on peut créer une entité dont la sémantique est différente (cette création entraînant souvent, mais pas toujours, la destruction de l'original). Une entité ne peut pas être copiée dans un autre type (avec un sémantique différente) et rester la même entité (donc avec la même sémantique). C'est impossible.

    Il y a une autre question sous-jacente : est-ce qu'il existe des classes dont la sémantique est un mix entre une sémantique d'entité et une sémantique de valeur ?

    Je pense que non, mais quand à le démontrer...
    [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.

  10. #10
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Hum...

    Le clonage nécessite bien un constructeur par copie (non public), non ? (personne ne m'a repris dessus, donc je pense que c'est correct).
    Dans ce cas, peut-on se contenter de l'écrire sans penser aux éventuelles classes filles (quitte à ce qu'il ne soit pas appelé par ces classes filles), ou y a-t-il des précautions à prendre ?


    Déplacer une entité ou échanger deux entités si les types ne sont pas les mêmes n'a pas de sens, soit.
    Mais s'ils sont identiques, ça peut avoir un sens, non ?
    Lorsque l'on veut placer une entité existante dans un conteneur, on n'a que deux moyens :
    • déplacer l'entité dans le conteneur ;
    • créer une entité temporaire dans le conteneur, puis échanger les deux entités.

    Non ? C'est absurde ce que je dis ?


    Bon, je vais déjà voir avec lien et les idées proposés par Luc Hermitte.
    Je vous tiens au courant.

  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
    Citation Envoyé par Steph_ng8 Voir le message
    a- Mais s'ils sont identiques, ça peut avoir un sens, non ?
    b- Lorsque l'on veut placer une entité existante dans un conteneur, on n'a que deux moyens :
    • déplacer l'entité dans le conteneur ;
    • créer une entité temporaire dans le conteneur, puis échanger les deux entités.
    a- non
    b- C'est pour cela que l'on dit aussi bien "reference VS value" que "entity VS value". La première opposition est technique, la seconde est sémantique. Et pourtant, l'approche qui marche, c'est celle qui consiste à les confondre.
    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 éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Décidément, je ne comprends pas.
    Tant qu'une entité reste entité (un « bloc »), quelle importance peut avoir l'adresse mémoire à laquelle elle est stockée ?

    Parce que c'est un peu ça ce que font le déplacement et l'échange, finalement.
    L'objet a conservé son intégrité avant et après, mais il n'est plus au même endroit.
    En quoi est-ce gênant ?

    Le plus important, ce sont les propriétés (au sens global) de l'objet, pas son emplacement mémoire, si ?

  13. #13
    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
    Tu n'as pas encore perçu en quoi une entité n'est pas une valeur.
    Ce qui distingue deux valeurs, c'est leur état. Elles sont égales ou pas.
    Ce qui distingue deux entités, c'est leur identité. Elles sont identiques ou pas.

    Et la façon la plus simple d'identifier deux entités, c'est au travers de leur adresse. Simple et efficace. Si tu commences à les déplacer, il faut stocker l'identité ailleurs et être capables de les tracker.

    Pour en revenir à la technique, un vector<EntiteMere> posera de gros soucis en termes de déplacement (slicing). Contrairement à un vector<EntiteMere*>. Moyen simple et efficace de résoudre tous nos problèmes, et de coller aux sémantiques.
    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...

  14. #14
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Citation Envoyé par Luc Hermitte Voir le message
    Tu n'as pas encore perçu en quoi une entité n'est pas une valeur.
    Ce qui distingue deux valeurs, c'est leur état. Elles sont égales ou pas.
    Ce qui distingue deux entités, c'est leur identité. Elles sont identiques ou pas.
    Ah...
    Vu comme ça, je crois que je commence à comprendre.


    Citation Envoyé par Luc Hermitte Voir le message
    Pour en revenir à la technique, un vector<EntiteMere> posera de gros soucis en termes de déplacement (slicing). Contrairement à un vector<EntiteMere*>.
    Oui, tu as raison.
    Je me suis un peu mélangé les pinceaux...

    L'exemple que j'ai donné est valable pour les classes non copiables, mais déplaçables (et échangeables), et sans polymorphisme.
    Du coup, je ne sais pas si c'est applicable aux entités (sans héritage)...

  15. #15
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    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
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par Steph_ng8 Voir le message
    Hum...
    Le clonage nécessite bien un constructeur par copie (non public), non ? (personne ne m'a repris dessus, donc je pense que c'est correct).
    C'est une implémentation possible, ce n'est pas la seule (même si, soyons sérieux, c'est la plus efficace).

    Citation Envoyé par Steph_ng8 Voir le message
    Dans ce cas, peut-on se contenter de l'écrire sans penser aux éventuelles classes filles (quitte à ce qu'il ne soit pas appelé par ces classes filles), ou y a-t-il des précautions à prendre ?
    Le code d'une fonction de clonage doit être présent dans chacune des classes dérivée de la classe mère. Une seule manque, et l'implémentation de la classe parent sera utilisée - et on se retrouve donc avec du slicing.

    Son écriture doit être correcte, c'est à dire se servir d'une fonctionnalité du C++ que peu connaissent et utilisent : la possibilité de jouer avec la covariance des types de retour des méthodes virtuelles (cf. http://en.wikipedia.org/wiki/Covariant_return_type)

    Pour simplifier, le code suivant est nécessaire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    class A 
    {
     A(const A& other) { ... }
    public:
     virtual A* clone() const { return A(*this); }
    };
     
    class B : public A
    {
      B(const B& other) : A(other) { ... }
    public:
      virtual B* clone() const { return B(*this); }
    }
    La fonction clone() n'existe qu'une seule fois dans la vtable - on fait référence à la même fonction, bien que le type de retour soit différent.

    Ce qui fait la validité de cette écriture, c'est que B dérive de A. B "hérite" d'une méthode clone() qui doit renvoyer une instance de A. Puisque un B est une instance de A, la méthode peut être "redéfinie" pour renvoyer une instance de B (c'est expliqué dans l'article wikipedia cité ci-dessus).

    En utilisant cette écriture, on s'assure que l'on va utiliser le constructeur par copie de la bonne classe, sans se préocupper des constructeurs par copie des classes parent.
    [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.

  16. #16
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Merci Emmanuel, c'est ce que je voulais savoir.
    En passant, je connaissais déjà la covariance du type de retour...

    Sinon, il y a un truc qui me chiffonne dans ton code.
    Si je me souviens bien de mes cours de C++, les éléments d'une class sont privés par défaut.
    Tes constructeurs par copie n'étant déclarés derrière aucun « opérateur » de visibilité, ils ont donc la visibilité par défaut, à savoir private.

    Si mes souvenirs sont toujours justes, un élément privé d'une classe n'est accessible nulle part en dehors de cette classe, sauf pour les amis.
    Même pas pour les éventuelles classes filles.

    Donc logiquement, on ne devrait pas pouvoir appeler le constructeur par copie de A à partir de B...

    C'est un oubli/une simplification de ta part, ou ça fonctionne et j'ai raté quelque chose ?

  17. #17
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    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
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par Steph_ng8 Voir le message
    Merci Emmanuel, c'est ce que je voulais savoir.
    En passant, je connaissais déjà la covariance du type de retour...

    Sinon, il y a un truc qui me chiffonne dans ton code.
    Si je me souviens bien de mes cours de C++, les éléments d'une class sont privés par défaut.
    Tes constructeurs par copie n'étant déclarés derrière aucun « opérateur » de visibilité, ils ont donc la visibilité par défaut, à savoir private.

    Si mes souvenirs sont toujours justes, un élément privé d'une classe n'est accessible nulle part en dehors de cette classe, sauf pour les amis.
    Même pas pour les éventuelles classes filles.

    Donc logiquement, on ne devrait pas pouvoir appeler le constructeur par copie de A à partir de B...

    C'est un oubli/une simplification de ta part, ou ça fonctionne et j'ai raté quelque chose ?
    Non, tu as raison, c'est une erreur. Il faut mettre au moins le constructeur de A en protected.
    [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. #18
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Bon, ce n'est plus tout à fait dans le sujet initial, mais j'ai trouvé des classes qui (selon moi) ont une sémantique de valeur dans hiérarchie à héritage publique (cf. le diagramme attaché).

    Functor est une classe représentée essentiellement par un nom (std::string) et une arité (int).
    Normalement elle a une sémantique de valeur.

    Une SymbolicConstant est un FunctionalTerm dont l'arité est 0.

    Les sous-classes de ArithmeticExpression ont une sémantique d'entité, ainsi que Variable.
    Pour FunctionalTerm, je ne suis pas encore sûr...

    D'un point de vue hors-POO :
    • Deux constantes numériques avec la même valeur sont identiques ;
    • Deux constantes symboliques avec la même valeur (donc le même nom) sont identiques ;
    • Deux constantes nommées avec le même nom ne doivent pas être considérées comme différentes ;
    • Deux constantes nommées avec des valeurs différentes ne peuvent pas coexister.

    Surprenant, n'est-il pas ?

    Dans ce cas, pourquoi :
    • deux instances de NumericalConstant avec la même valeur de value devraient être considérées comme différentes si elles ne représentent pas la même entité ?
    • deux instances de SymbolicConstant avec la même valeur de functor->name devraient être considérées comme différentes si elles ne représentent pas la même entité ?
    • deux instances de NamedConstant avec la même valeur de name (et de value) devraient être considérées comme différentes si elles ne représentent pas la même entité ?


    Pour moi, NumericalConstant, SymbolicConstant et NamedConstant ont une sémantique de valeur.

    En passant, on manipule tout par des Term*.
    Images attachées Images attachées  

  19. #19
    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
    Hé non, c'est justement là que tu te trompes...

    Ou bien, tu manipules les objets selon leur type réel, et, tout ce que tu as fait, c'est créer un héritage inutile qui n'est sans doute pas loin de violer LSP (ce qui devrait signifier le retrait de l'héritage et donc, effectivement, le retrour à une sémantique de valeur ) , ou bien, tu les utilises sous la forme de pointeur sur Term, et dans, ce cas, ils ont bel et bien sémantique d'entité parce que tu utilisera toujours un pointeur sur le meme objet pour accéder à un objet particulier
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  20. #20
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Oups, j'ai oublié RangeTerm, qui est comme les trois classes incriminées.

    En fait, à chacune de ces quatre classes correspond un conteneur qui stocke les instances de manière unique.
    Enfin quand je dis « de manière unique », je veux dire que je vérifie d'abord si un objet avec la même valeur n'est pas déjà présent.

    Disons qu'on recherche les objets par leur valeur, et si un objet correspondant existe déjà, on retourne son adresse, sinon on en crée un (et on retourne son adresse...).
    À l'utilisation, une fois les instances créées, on utilise ces classes comme des classes à sémantique d'entité (via des pointeurs).
    Mais à la création, elles se comportent un peu comme des classes à sémantique de valeur.

    ...Non ?

Discussions similaires

  1. [Débutant] Copie, déplacement et suppression d'un fichier en c#
    Par garfieldlcht dans le forum C#
    Réponses: 13
    Dernier message: 18/06/2015, 12h31
  2. Réponses: 5
    Dernier message: 03/12/2006, 15h55
  3. Réponses: 9
    Dernier message: 09/11/2006, 10h10
  4. Réponses: 8
    Dernier message: 10/09/2005, 20h12

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