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 :

move semantic et attributs const


Sujet :

C++

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut move semantic et attributs const
    Bonjour à tous,

    J'ai un petit problème avec la sémantique de déplacement émulée par boost::move (le sandbox, download). Les objets de mon projet ont tous un identifiant et un nom, vu que mes objets ont une sémantique d'entité, ils ressemblent à ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class UnObjet : private boost::noncopyable
    {
    public:
      UnObjet(std::string const& _name, size_t const _id) : name(_name), id(_id)
      {}
     
      std::string const name;
      size_t const id;
    };
    C'est pratique et ça évite des erreurs. Maintenant je voudrais utiliser la sémantique de déplacement pour stocker ces objets dans une collection, j'ai donc défini un constructeur par déplacement:
    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
     
    class UnObjet : private boost::noncopyable
    {
    private:
      BOOST_MOVABLE_BUT_NOT_COPYABLE(UnObjet)
    public:
      UnObjet(std::string const& _name, size_t const _id) : name(_name), id(_id)
      {}
     
      UnObjet(BOOST_RV_REF(UnObjet) o) : 
         name(boost::move(o.name)), id(boost::move(o.id))
      {}
     
      std::string const name;
      size_t const id;
    };
     
    int main(void)
    {
      using namespace boost::container;
      vector<UnObjet> v;
      UnObjet o0 (0, "zero");
      UnObjet o1 (1, "un");
      v.push_back(boost::move(o0));
      v.push_back(boost::move(o1));
      assert(v.size() == 2);
      return 0;
    }
    Jusque là, tout va bien, mon problème c'est que si maintenant j'essaie de déplacer le vecteur que je viens de construire, le compilateur m'indique qu'il me faudrait l'opérateur "move assign" (affectation par déplacement?) et là, c'est le drame puisque du fait de mes membres const, je ne peux pas définir un opérateur "move assign".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    UnObjet& operator=(BOOST_RV_REF(UnObjet) o)
    {
      id = boost::move(o.id);
      return *this;
    }
    (Le compilateur me jette avec: l-value définit un objet const)

    J'ai actuellement deux pistes pour trouver une solution:
    (1) ne plus utiliser de vecteur mais des listes
    (2) virer mes const, mettre mes attributs en privé et faire des accesseurs (et donc je pourrais alors définir mon move assign)

    Que pensez-vous de ces solutions? Avez vous des infos sur les prérequis des conteneurs move-aware (constructible par défaut, déplaçables par copie, déplaçables par affectation, ...)?

    Edit: Je viens de faire un test, une dernière possibilité est de passer par des const_cast dans l'opérateur d'affectation par déplacement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    UnObjet& operator=(BOOST_RV_REF(UnObjet) o)
    {
      const_cast<size_t&>(id) = boost::move(o.id);
      const_cast<std::string&>(name) = boost::move(o.name);
      return *this;
    }

  2. #2
    Membre émérite

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Points : 2 252
    Points
    2 252
    Par défaut
    Citation Envoyé par CedricMocquillon Voir le message
    Jusque là, tout va bien
    Certes, mais je ne suis pas sûr que le code fasse ce que tu avais prévu...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    #include <iostream>
    struct B
    {
       B(){}
       B(const B&){std::cout << "copy ctor" << std::endl;}  
       B(B&&){std::cout << "move ctor" << std::endl;}
    };
    int main()
    {
       const B b1;
       B b2(std::move(b1));
    }
    Ce code affiche "copy ctor"...

    A mon sens, la consteness et la sémantique de déplacement sont profondément incompatibles.

    Le but de la sémantique de déplacement, c'est que l'objet cible soit construit en raflant les ressources de l'objet source, qui se retrouve vidé de sa substance et meurt. Si l'objet source est const il est impossible de le modifier, et seule sa copie est permise.

    C'est ce qui se passe ici :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    UnObjet(BOOST_RV_REF(UnObjet) o) : 
         name(boost::move(o.name))
    Impossible de déplacer car o.name est const, donc le constructeur de la string se rabat sur le constructeur par copie. (et sans prévenir!).

    Edit: Je viens de faire un test, une dernière possibilité est de passer par des const_cast dans l'opérateur d'affectation par déplacement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    UnObjet& operator=(BOOST_RV_REF(UnObjet) o)
    {
      const_cast<size_t&>(id) = boost::move(o.id);
      const_cast<std::string&>(name) = boost::move(o.name);
      return *this;
    }
    Un cache-misère, comme souvent avec const_cast.
    Ce code fait une copie de la string name, comme expliqué plus haut...

    Avez vous des infos sur les prérequis des conteneurs move-aware (constructible par défaut, déplaçables par copie, déplaçables par affectation, ...)?
    Il me semble que dans le draft de la nouvelle norme, les objets placés dans un vector doivent être au minimum Assignable (avoir un opérateur=), que ce soit CopyAssignable ou MoveAssignable.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut
    Merci pour ta réponse!

    En fait, je n'ai peut être pas été très clair dans mon exemple (je débute en move semantic ).

    Le but de la sémantique de déplacement, c'est que l'objet cible soit construit en raflant les ressources de l'objet source, qui se retrouve vidé de sa substance et meurt. Si l'objet source est const il est impossible de le modifier, et seule sa copie est permise.
    Je suis d'accord avec ça, le seul truc c'est que si l'objet a des attributs const mais n'est pas constant, la sémantique de déplacement indiquant que l'objet source va être "vidé de sa substance", on serait en droit d'écrire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    UnObjet(BOOST_RV_REF(UnObjet) o) : 
         name(boost::move(const_cast<std::string&>(o.name))),
         id(boost::move(const_cast<size_t&>(o.id)))
      {}
    pour bénéficier à la fois de la "sécurité" apportée par la constenesse des membres et l'efficacité du déplacement (ici l'optimisation portera, uniquement sur le string).

    Bon par contre c'est sûr, ça oblige à utiliser un const_cast ce qui n'est pas franchement recommandé ("Un cache-misère") pour autant, je trouve que ce serait ici parfaitement justifié (n'hésitez pas à réagir sur ce point).

    En gardant cette logique, l'opérateur d'affectation par déplacement deviendrait:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    UnObjet& operator=(BOOST_RV_REF(UnObjet) o)
    {
      const_cast<size_t&>(id) = boost::move(const_cast<size_t&>(o.id));
      const_cast<std::string&>(name) = boost::move(const_cast<std::string&>(o.name));
      return *this;
    }
    Ce qui, je l'accorde est plutôt dérangeant. Après, je reste ouvert à d'autres propositions, mais au final je me dit que soit:
    (1) c'est un défaut de conception de mettre des attributs const et je dois donc mettre mes attributs en privé et faire des accesseurs m'évitant tout ces const_cast!
    (2) c'est possible d'utiliser des attributs const et donc dans ce cas, si je veux pouvoir déplacer l'objet en question, vive les const_cast!

    Tout éclairage est le bien venu pour trancher entre ces deux propositions...

  4. #4
    Membre chevronné
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Points : 2 205
    Points
    2 205
    Par défaut
    Intuitivement j'aurais tendance à mettre les membres en privée et non const. D'ailleurs pas forcément assorti de l'accesseur / mutateur adéquat.
    Là preuve sinon tu dois arriver à un const_cast<>. (d'un autre côté j'ai encore la tête dans le c** =) )
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  5. #5
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Points : 546
    Points
    546
    Par défaut
    Bonjour,
    autant je comprends le constructeur (l'idée ne me semble pas incorrecte)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    UnObjet(BOOST_RV_REF(UnObjet) o) : 
         name(boost::move(const_cast<std::string&>(o.name))),
         id(boost::move(const_cast<size_t&>(o.id)))
      {}
    autant j'ai des doutes sur l'opérateur d'affectation.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    UnObjet& operator=(BOOST_RV_REF(UnObjet) o)
    {
      const_cast<size_t&>(id) = boost::move(const_cast<size_t&>(o.id));
      const_cast<std::string&>(name) = boost::move(const_cast<std::string&>(o.name));
      return *this;
    }
    Placer des membres en const signifie que tu ne veux plus pouvoir les modifier une fois l'objet créé. Or ici tu sembles vouloir le permettre sous pretexte que l'objet en paramètre est une r-value.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    UnObjet  A("A",1);
    // A.string == "A" et A.id == "1" 
    // A.string et A.id étant const, il ne devraient plus pouvoir changer de valeur
    UnObjet B("B",2);
    A = boost::move(B);
    // A.string == "B" ?!? et A.id == "2" ?!?
    La move sémantique n'est pas sensée casser ce genre de concept existant il me semble .

  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
    Salut,

    Peut être serait-il temps de te poser la question réelle de l'intérêt d'avoir des membres publiques constants...

    En effet, tu n'es absolument pas obligé de créer des mutateurs ("setter") pour tes membres, s'il s'avère que ce n'est pas utile: si id et name ne doivent pas être modifiés une fois que ton objet est construit, rien ne t'empêche de n'exposer que des accesseurs ("getter"), et tu ne perd absolument pas au change:
    • l'inlining aidant, l'accès au membre depuis un accesseur ne sera pas plus long
    • La constance de l'accesseur et de la valeur renvoyée évite d'aller modifier le membre, tout en permettant malgré tout d'y accéder, en lecture, depuis un objet aussi bien constant que non constant
    • Tu évites d'avoir des const_cast dans tous les coins, ce qui reste, malgré tout, souvent le symptome du fait que tu as "loupé quelque chose"
    • Tu peux implémenter ta sémantique de mouvement sans aucun problème
    Bref, il n'y a que du bon à l'envisager sous cet angle

    Bien sur, tu pourrais me dire qu'il faut alors ajouter une paire de parenthèse chaque fois que tu souhaite accéder à un des membres de l'objet, mais, si tu t'arrête à ce genre de détail, il est peut être temps de boire une tisane à la camomille et d'aller se reposer
    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 averti
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    301
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 301
    Points : 345
    Points
    345
    Par défaut
    @Goten : pour moi, un membre public const est équivalent à un membre privé avec accesseur (mais pas de mutateur du fait du const).

    @gb_68 : je me serai bien passé de l'opérateur d'affectation par déplacement, le seul truc c'est que les objets d'un vecteur doivent être assignable

    @koala01 : ok dans le principe, je pense effectivement me diriger vers cette solution; le seul truc c'est qu'au final je vois plus trop l'intérêt d'avoir de membre constant (dans le cas général), je suis alors tenté de l'interpréter comme une mauvaise pratique.

    PS: pour koala01, j'étais en train de rédiger ma réponse à Goten et gb_68, y'a donc un peu de redite dans ma réponse par rapport à ton message

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par CedricMocquillon Voir le message
    @koala01 : ok dans le principe, je pense effectivement me diriger vers cette solution; le seul truc c'est qu'au final je vois plus trop l'intérêt d'avoir de membre constant (dans le cas général), je suis alors tenté de l'interpréter comme une mauvaise pratique.
    Il reste toujours le cas des membres statiques

    Etant indépendant de toute instance de la classe, ils ne devraient pas être impliqués dans le processus de la sémantique de mouvement (à vérifier avec ce que dit C++0x à ce sujet )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Points : 546
    Points
    546
    Par défaut
    Citation Envoyé par CedricMocquillon Voir le message
    @gb_68 : je me serai bien passé de l'opérateur d'affectation par déplacement, le seul truc c'est que les objets d'un vecteur doivent être assignable
    C'est vrai que tu l'as introduit à cause de la contrainte que les vecteurs t'imposaient (Assignable).

    Mais devoir passer outre le const pour pouvoir utiliser les vecteurs, c'est pas génial comme solution (l'opérateur étant ensuite disponible dans tout le reste du code).
    Le même problème survient quand l'objet possède des références : on ne peut pas définir un opérateur d'affectation correcte.

    Les vecteurs auront surtout besoin d'un constructeur par déplacement (qui remplacera celui par copie, actuellement nécessaire) et du destructeur lors de l'expansion/la réduction de ceux-ci.
    L'opérateur d'assignement doit sans doute être requis pour le déplacement interne des objets dans le vecteur (avec erase par exemple). C'est dommage que cette contrainte reste pour les vecteurs...

    Cela aurait pu être également réalisé par destruction puis placement new (peut être moins performants). Mais cela est aussi moins exception safe.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Un objet qui peut avoir des membres const ne devrait pas être assignable (ou du moins, ne devrait pas tenter de modifier ces membres-là lors d'une assignation).
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

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

  11. #11
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Points : 546
    Points
    546
    Par défaut
    Je viens de mieux comprendre la "move" sémantique. En fait un objet qui a été déplacé n'est pas détruit pour autant. Il est dans une sorte d'état "vide" (encore que - je n'ai toujours pas compris le swap dans l'implémentation d'exemple de l'opérateur= rvalue ref. vue aussi ici [Edit] c'est peut être une erreur -> l'operator= par copie est aussi buggé [/Edit]).
    Cet objet vide doit supporter le destructeur de la classe ainsi que l'opérateur d'affectation - au vu de la nouvelle implémentation du swap.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <class T> swap(T& a, T& b)
    {
        T tmp(std::move(a));
        a = std::move(b);   
        b = std::move(tmp);
    }
    Le fait de pourvoir modifier une rvalue référence (par exemple pour "vider" l'objet) rend inéligible à la move sémantique tout objet qui possède un membre non mutable, tel que const ou référence.

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

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Citation Envoyé par gb_68 Voir le message
    Le fait de pourvoir modifier une rvalue référence (par exemple pour "vider" l'objet) rend inéligible à la move sémantique tout objet qui possède un membre non mutable, tel que const ou référence.
    En es-tu sûr?
    On peut faire une move semantic sans modifier tous les membres de l'objet source. Généralement, on ne modifie que les membres qui référencent une ressource...
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

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

  13. #13
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Points : 546
    Points
    546
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    En es-tu sûr?
    Non .
    Citation Envoyé par Médinoc Voir le message
    On peut faire une move semantic sans modifier tous les membres de l'objet source. Généralement, on ne modifie que les membres qui référencent une ressource...
    Oui ce serait possible, mais cela donnerait des effets surprenants. Il serait possible de réaliser un swap entre ces deux objets, qui verraient une partie de leurs membres échangés mais pas l'autre.
    Je pense que ce serait comme écrire - actuellement - un constructeur par copie qui modifie l'objet à copier ; ce n'est une pratique correcte que dans très peu de cas (ex. auto_ptr, mais justement bientôt remplacé par unique_ptr).

Discussions similaires

  1. "move semantic" testée avec gcc tdm 4.8.1
    Par keitaro42300 dans le forum C++
    Réponses: 4
    Dernier message: 16/12/2013, 00h09
  2. Disponibilité des "move-semantics" ?
    Par cob59 dans le forum Langage
    Réponses: 8
    Dernier message: 17/06/2013, 20h10
  3. Réponses: 6
    Dernier message: 06/06/2013, 11h23
  4. [C++0x] Move semantics + NRVO
    Par Florian Goo dans le forum Langage
    Réponses: 16
    Dernier message: 04/05/2009, 15h44
  5. rendre static const un attribut hérité ?
    Par JujuTh dans le forum C++
    Réponses: 6
    Dernier message: 08/07/2007, 16h02

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