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 :

[C++0x] Move semantics + NRVO


Sujet :

Langage C++

  1. #1
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut [C++0x] Move semantics + NRVO
    Bonjour les développeurs,

    Soit 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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    #include <iostream>
     
    class movable
    {
    	public:
    		movable(int){}
     
    		movable(const movable&) = delete;
     
    		movable(movable&&)
    		{
    			std::cout << "move\n";
    		}
     
    		const movable&
    		operator=(const movable&) = delete;
     
    		const movable&
    		operator=(movable&&) = delete;
    };
     
    movable
    f()
    {
    	return movable(0);
    }
     
    int
    main()
    {
    	std::cout << "test1:\n";
    	movable o1 = f();
     
    	std::cout << "test2:\n";
    	movable o2(f());
     
    	std::cout << "test3:\n";
    	movable o3(std::move(f()));
     
    	return 0;
    }
    On a une classe non copiable mais movable et une fonction qui renvoie un objet de cette classe. Dans le main(), j'effectue trois appels à cette fonction, en effectuant respectivement une assignation par copie, une construction par copie et une construction par mouvement.

    Sachant que j'ai désactivé la construction par copie et l'opérateur d'assignation, je m'attends à ce que le compilateur refuse les tests 1 et 2. Seulement non, aucun problème de compilation.
    Étant donné que j'ai la sortie suivante, j'imagine que c'est la NRVO qui permet ce comportement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    test1:
    test2:
    test3:
    move
    Si je désactive la NRVO (-fno-elide-constructors), des déplacements sont effectués dans tous les cas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    test1:
    move
    move
    test2:
    move
    move
    test3:
    move
    move
    On a donc un code qui compile seulement dans le cas où le compilateur supporte la NRVO.
    C'est pas un peu discutable comme comportement ? À moins que cette optimisation ait été prise en compte dans le nouveau standard ?

    J'hésite à remonter ce comportement comme un bug. Vous en pensez quoi ?


    Voici le Makefile pour ceux qui veulent tester (GCC 4.4 requis) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    test: main.cpp 
    	g++-4.4 -o test main.cpp -W -Wall -ansi -pedantic -std=c++0x -fno-elide-constructors
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  2. #2
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    On a donc un code qui compile seulement dans le cas où le compilateur supporte la NRVO.
    Ton code compile dans les deux cas.
    Je vois pas où est le problème. Dans le premier cas les déplacements sont évités par l'optimisation NRVO, dans le second elles ne le sont pas, ce qui est tout à fait normal.

    On a une classe non copiable mais movable et une fonction qui renvoie un objet de cette classe. Dans le main(), j'effectue trois appels à cette fonction, en effectuant respectivement une assignation par copie, une construction par copie et une construction par mouvement.
    Le problème, c'est que f() est une rvalue... Tes trois tests sont en fait équivalents. (sauf que le déplacement explicite de std::move n'est pas éliminable par NRVO)
    Par ailleurs, tu n'effectues aucune assignation.
    Boost ftw

  3. #3
    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
    Oui, loufoque a raison.
    Ta fonction f revoie un "movable" temporaire, c'est une dire une rvalue. Donc les deux premiers test font la même chose et le troisième est un peu bizarre vu que le std::move prend une rvalue de type "movable" et qu'il renvoie une référence sur cette rvalue (un movable&&) sans qu'on sache bien à quoi ça sert. Le constructeur par mouvement se chargera tout seul de prendre une référence sans qu'on est besoin de lui donner explicitement. Exactement comme avec les lvalue references d'ailleurs.
    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 T
    {
       T(const T&);
    }
     
    T& copy(T t)
    {
       static_cast<T&>(t);
    }
     
    T t1;
    T t2 = copy(t); // pas besoin !
    T t3 = t; // ok
    En résumé :
    T : une valeur. Elle peut être soit une lvalue, soit une rvalue.
    lvalue : qui peut se mettre à gauche de l'opérateur = et qui a un nom.
    Dans ce code, t est une lvalue :
    rvalue : qui peut se mettre à droite de l'opérateur = et qui n'a pas de nom (car temporaire)
    Dans ce code la valeur de retour de f est une rvalue (et je suis obligé de dire "valeur de retour de f", car elle n'a pas de nom.)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    T f()
    {
       T t;
       return t;
    }
    T& : référence sur une lvalue. Ne peut se binder qu'avec des lvalue.
    T&& : référence sur une rvalue. Ne peut se binder qu'avec des rvalue.

    Ce qui veut dire que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    T&& f()
    {
       T t;
       return t;
    }
    est une erreur au même titre que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    T& f()
    {
       T t;
       return t;
    }
    Dans les deux cas, f renvoie une référence sur la valeur locale t.
    D'ailleurs gcc se plaint "warning: reference to local variable 't' returned"

    On a donc un code qui compile seulement dans le cas où le compilateur supporte la NRVO.
    Non, ce qui se passe ici, c'est que l'optimisation NRVO court-circuite complètement le constructeur par copie et/ou le constructeur par mouvement, et construit directement la variable d'arrivé en une seul fois. C'est le cas optimal.

  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
    Question qui sort un peu du contexte mais tant qu'a faire :
    Ici le mot clef delete a quel impact, a quoi sert il dans ce contexte ?


    A noter que les deux premiers test font appel au constructeur de recopie... Il y'a pas d'affectation quoi.
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  5. #5
    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
    Delete permet d'officialiser l'astuce qui consistait à déclarer le constucteur par copie en private sans le définir, histoire que le linker se gauffre si quelqu'un tente de faire une copie. Donc d'interdire la copie. C'était un peu de la bidouille quand même.

    L'avantage de delete, c'est que l'erreur à lieu à la compilation, et que le message d'erreur est beaucoup moins cryptique qu'une erreur de linker.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class noncopyable()
    {
    public:
       noncopyable() = default;
       noncopyable(const noncopyable&) = delete;
    } 
     
    noncopyable n1;
    noncopyable n2(n1);
    error: deleted function 'noncopyable::noncopyable(const noncopyable&)'
    error: used here (pointe la ligne "noncopyable n2(n1);" )

  6. #6
    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
    Hum oki merci . Encore un truc :
    T'aurais un bon article sur les move semantics? Parce que bon plusieurs fois que je tente cette feature de la prochaine norme mais j'ai du mal a voir l'intérêt. Notamment ici permettre de faire des choses qu'on a a première vue interdit. ( En déclarant l'op= et le ctor par recopie privée).
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  7. #7
    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
    T'aurais un bon article sur les move semantics?
    Le meilleur article que je connaisse sur la question, c'est celui-ci (attention, c'est du costaud, il fait presque trente pages ! A digérer petit à petit.)
    Le seul problème, c'est qu'il a été écrit avant un changement assez important sur la manière dont les références peuvent se binder.

    En résumé :

    Avant : règles hyper complexes, détaillées dans l'article.

    Entre temps, des problèmes de sécurité ont été découverts du à ces règles (si je me souviens bien, il pouvait y avoir dans certain cas des move silencieux).

    Maintenant : règle beaucoup plus strictes, et beaucoup plus simple, à savoir :
    Les reference & ne peuvent se binder qu'avec des lvalue.
    Les reference && ne peuvent se binder qu'avec des rvalues.
    Aucune conversion implicite n'est autorisée.

    Parce que bon plusieurs fois que je tente cette feature de la prochaine norme mais j'ai du mal a voir l'intérêt.
    Je suis un peu dans le même cas. La plupart des articles se focalise beaucoup sur le coté technique. Il est difficile de se faire une idée des pratiques qui émergeront de tout ça.

    D'ailleurs je suis curieux. Florian ? Pourrais-tu détailler un peu dans quel cadre tu utilises la sémantique de mouvement ? Un exemple réel serait surement plus parlant.... Car de mon coté je n'arrive pas vraiment à imaginer des cas d'utilisation où l'on aurait besoin de "transférer" des objets non-copiables.

  8. #8
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Merci pour vos réponses

    Ouch, oui, pour le second cas j'avais effectivement un doute, mais je me suis surtout laissé induire en erreur par le premier cas. J'avais oublié qu'il ne s'agit absolument pas d'une affectation…

    Je suis également déstabilisé par ce type de code (toujours avec la même classe movable) :
    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
     
    void
    f(movable&& m)
    {
    	movable m2(m); //(A)
    }
     
    int
    main()
    {
    	movable m(0);
    	f(m); //(B)
     
    	return 0;
    }
    Ce code ne compile pas, non pas à cause de la ligne (B), mais de la ligne (A) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    main.cpp: In function ‘void f(movable&&)’:
    main.cpp|8| erreur: deleted function ‘movable::movable(const movable&)’
    main.cpp|25| erreur: used here
    Le code correct est donc celui-ci :
    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
     
    void
    f(movable&& m)
    {
    	movable m2(std::move(m));
    }
     
    int
    main()
    {
    	movable m(0);
    	f(m);
     
    	return 0;
    }
    1°) Le m de main() n'est pas un temporaire (donc pas une rvalue, si j'ai bien tout suivi), pourtant f() l'accepte tel quel.
    Je suppose que cela s'explique par le fait qu'il n'y a aucune surcharge de f() prenant en paramètre une lvalue-reference (le cas échéant, ce serait cette surcharge qui serait appelée). Le test que je viens d'effectuer semble confirmer cette explication.

    2°) Le m de f() est explicitement une référence de rvalue, pourtant il faut faire un std::move() pour que le compilateur comprenne qu'on cherche à appeller le constructeur par déplacement et non le constructeur par copie. Comment expliquer cela ?


    Ahlala, ces rvalue-references… parfois j'ai l'impression d'avoir tout compris, et la seconde d'après je me laisse totalement dérouter !


    Edit : J'ai commencé à taper ma réponse avant d'avoir pu lire ton dernier message, Arzar. Je m'attèle de suite à te trouver un exemple concret !
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  9. #9
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    J'utilise principalement la sémantique de mouvement pour la construction d'objets composés d'autres objets dont la copie serait couteuse.
    Dans le cas suivant, il aurait fallut passer par des pointeurs et des allocations dynamiques pour éviter des copies (ou alors une bidouille du style passer des fabriques en paramètre à la voiture, mais c'est un peu lourd à mettre en place et conceptuellement c'est pas top).
    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
    30
    31
    32
    33
    34
    35
    36
    37
     
    class voiture
    {
    	public:
    		voiture
    		(
    			moteur&& un_moteur,
    			roues&& des_roues,
    			portes&& des_portes,
    			volant&& un_volant
    		):
    			moteur_(std::move(un_moteur)),
    			roues_(std::move(des_roues)),
    			portes_(std::move(des_portes)),
    			volant_(std::move(un_volant))
    		{
    		}
     
    	private:
    		moteur moteur_;
    		roues roues_;
    		portes portes_;
    		volant volant_;
    };
     
    int
    main()
    {
    	moteur m = fabrique_moteur();
    	roues r = fabrique_roues();
    	portes p = fabrique_portes();
    	volant v = fabrique_volant();
     
    	voiture notre_voiture(m, r, p, v);
     
    	return 0;
    }
    Exemple complet en pièce-jointe.
    Fichiers attachés Fichiers attachés
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  10. #10
    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 Florian Goo Voir le message
    1°) Le m de main() n'est pas un temporaire (donc pas une rvalue, si j'ai bien tout suivi), pourtant f() l'accepte tel quel.
    Pareil chez moi avec un snapshot de GCC datant du 24 février.
    Il me semble que ce comportement fait justement partit des anciennes règles de "bindage", que perso je n'ai jamais vraiment réussit à comprendre.
    Heureusement, le nouveau comportement est beaucoup plus simple et il est décrit dans ce papier (n2812) qui date de décembre.

    We propose to prohibit rvalue references from binding to lvalues. Therefore, an rvalue reference will always refer to an rvalue or to an lvalue that the user has explicitly transformed into an rvalue (e.g., through the use of std::move). This makes the overload sets used in the copy/move idiom degrade safely when either of the overloads is removed for any reason. For example, with this change, given just a single function template enqueue:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template <class T, typename Cont>
      void enqueue(queue<T, Cont>& dest, queue<T, Cont>&& src); // #4
    calling enqueue with an rvalue succeeds while calling it with an lvalue fails:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    queue<string, list<string>> dest;
    queue<string, list<string>> src;
    enqueue(dest, src); // error: cannot bind rvalue reference in #4 to lvalue 'src'
    enqueue(dest, queue<string, list<string>>()); // okay, calls #4 to move from temporary to dest
    Edit : il me semble qu'il y avait une histoire de "dégradation" dans les anciennes règles, ce qui expliquerait peut être que la référence movable&& (m) se dégrade automatiquement en référence movable&, lors de l'appel de f avec une lvalue (m0)...

  11. #11
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Excellente nouvelle, cette révision. J'approuve moultement
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  12. #12
    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
    Citation Envoyé par Arzar Voir le message
    Le meilleur article que je connaisse sur la question, c'est celui-ci (attention, c'est du costaud, il fait presque trente pages ! A digérer petit à petit.)
    Le seul problème, c'est qu'il a été écrit avant un changement assez important sur la manière dont les références peuvent se binder.

    En résumé :

    Avant : règles hyper complexes, détaillées dans l'article.

    Entre temps, des problèmes de sécurité ont été découverts du à ces règles (si je me souviens bien, il pouvait y avoir dans certain cas des move silencieux).

    Maintenant : règle beaucoup plus strictes, et beaucoup plus simple, à savoir :
    Les reference & ne peuvent se binder qu'avec des lvalue.
    Les reference && ne peuvent se binder qu'avec des rvalues.
    Aucune conversion implicite n'est autorisée.


    Je suis un peu dans le même cas. La plupart des articles se focalise beaucoup sur le coté technique. Il est difficile de se faire une idée des pratiques qui émergeront de tout ça.

    D'ailleurs je suis curieux. Florian ? Pourrais-tu détailler un peu dans quel cadre tu utilises la sémantique de mouvement ? Un exemple réel serait surement plus parlant.... Car de mon coté je n'arrive pas vraiment à imaginer des cas d'utilisation où l'on aurait besoin de "transférer" des objets non-copiables.
    Merci pour l'article. En fait je l'avais déjà lu (en plusieurs fois) y étant tombé dessus suite à la lecture du premier article sur les lambda expressions et le mot clef auto. (excellent article d'ailleurs). Seulement et d'un je le trouvais assez hardu de prime abord et j'avais aussi entendu comme tu l'as dis que des changements avaient été apporté depuis l'article.
    Du coup je vais suivre ce topic avec attention.
    "Hardcoded types are to generic code what magic constants are to regular code." --A. Alexandrescu

  13. #13
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    Une fois qu'une rvalue est nommée, ça devient une lvalue.
    Normal.

    C'est pour ça que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void
    f(movable&& m)
    {
    	movable m2(m); //(A)
    }
    fait une erreur...
    Boost ftw

  14. #14
    Membre éclairé
    Avatar de Florian Goo
    Profil pro
    Inscrit en
    Septembre 2008
    Messages
    680
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Septembre 2008
    Messages : 680
    Points : 858
    Points
    858
    Par défaut
    Vu comme ça c'est vrai que ça semble plus logique.
    Il s'agit du comportement final ?
    Cours : Initiation à CMake
    Projet : Scalpel, bibliothèque d'analyse de code source C++ (développement en cours)
    Ce message a été tapé avec un clavier en disposition bépo.

  15. #15
    yan
    yan est déconnecté
    Rédacteur
    Avatar de yan
    Homme Profil pro
    Ingénieur expert
    Inscrit en
    Mars 2004
    Messages
    10 033
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Ingénieur expert
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mars 2004
    Messages : 10 033
    Points : 13 968
    Points
    13 968
    Par défaut
    Citation Envoyé par Goten Voir le message
    Parce que bon plusieurs fois que je tente cette feature de la prochaine norme mais j'ai du mal a voir l'intérêt.
    (Si je me trompe pas) Cela aura une utilité avec les algorithms.
    Par exemple le std::sort qui utilise std::swap. Seulement par défaut le swap utilise des copies.
    Actuellement, une solution est de spécialisé le std::swap pour ta classe.
    Avec move, y aura rien à faire.

  16. #16
    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 loufoque Voir le message
    Une fois qu'une rvalue est nommée, ça devient une lvalue.
    Normal.
    C'est pour ça que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    void
    f(movable&& m)
    {
    	movable m2(m); //(A)
    }
    fait une erreur...
    Je ne comprends pas pourquoi la "rvalueness" de la référence (T&&) entre en jeu...

    Le constructeur par mouvement a besoin de s'assurer que l'objet T qu'on lui passe est une rvalue. OK, c'est logique. (car dans ce cas l'objet est forcement temporaire et donc peut être modifié sans vergogne vu qu'il va disparaitre à la fin de l'expression) Et ça tombe bien, une rvalue reference nous assure justement que l'objet lié est une rvalue ! Je m'attendrais donc à ce que le constructeur par mouvement accepte :

    * T quand c'est une rvalue. (avec création automatique d'une T&&)
    * N'importe quelles T&&

    Ce qui est ennuyeux, c'est qu'il semble que l'on ne puisse plus trop se fier au système de type dans ce genre de cas. Une T&& rvalue déclenche comme prévu le constructeur par mouvement T(T&&), par contre une T&& lvalue appelle le constructeur par copie T(const T&) !

    Je suppose que cette règle spéciale a été conçu avant tout pour forcer le programmeur à écrire des std::move au moindre pas et éviter ce genre de cas...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void
    f(movable&& m)
    {
       movable m2(m); // move "silencieux"
       movable m3(m); // erreur
    }
    Sinon, loufoque, tu confirmes que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    struct T
    {
       T() = default;
       T(const T&) = delete;
    };
     
    f(T&& t)
    {
    ...
    }
     
    T t;
    f(t); // compile avec GCC du 14 février
    ne devrait pas compiler avec le draft actuel ?
    Dans ce cas, justement, on s'attendrait à devoir ajouter un std::move pour forcer la conversion lvalue/rvalue : f(std::move(t));

    Citation Envoyé par Florian Goo
    J'utilise principalement la sémantique de mouvement pour la construction d'objets composés d'autres objets dont la copie serait couteuse.
    Dans le cas suivant, il aurait fallut passer par des pointeurs et des allocations dynamiques pour éviter des copies (ou alors une bidouille du style passer des fabriques en paramètre à la voiture, mais c'est un peu lourd à mettre en place et conceptuellement c'est pas top).
    Joli.
    J'aime bien l'idée "d'assembler" un objet en à partir de ses sous-composants. C'est assez naturel quand on y pense.
    Une autre application très intéressante de la sémantique de mouvement m'est revenue en tête entre-temps : le pointeur intelligent unique_ptr. Qui est en réalité un wrapper RAII fin comme du papier à cigarette. Quasiment aucun surcout, très pratique quand on sait que notre ressource ne sera pas partagée. (Il pourrait d'ailleurs servir pour l'exemple de la voiture dans l'implémentation de moteur, roues, portes et volant)

    J'imagine qu'un code comme celui-ci...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    // C++98
    template <typename T>
    class Image
    {
    // Penser à interdire la copie et à fournir le destructeur.
    ...
       T* pixels_;
    }
    ...Pourra être modifié sans autre forme de procès en :
    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
     
    //c++0x
    template <typename T>
    class Image
    {
       // pas besoin de constructeur par copie,
       //  de constructeur par mouvement, de destructeur
       std::unique_ptr<T> pixels_;
    }
     
    // template aliases, Yay ! (Ne compile pas encore avec GCC)
    template<typename T>
    using ImageSP = std::shared_ptr<Image<T>>;
     
    template<typename T>
    using ImageUP = std::unique_ptr<Image<T>>;
     
    int main()
    {
        ImageSP<int> im = ImageSP<int>(new Image<int>());
        ImageSP<int> im2 = im;
     
        ImageUP<int> im3 = ImageUP<int>(new Image<int>());
        ImageUP<int> im4 = std::move(im3);
    }

  17. #17
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Points : 4 625
    Points
    4 625
    Par défaut
    Ce qui est ennuyeux, c'est qu'il semble que l'on ne puisse plus trop se fier au système de type dans ce genre de cas. Une T&& rvalue déclenche comme prévu le constructeur par mouvement T(T&&), par contre une T&& lvalue appelle le constructeur par copie T(const T&) !
    Le type est la lvalue/rvalue-ness sont des choses assez orthogonales.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void foo(int&& i)
    {
        // i a pour type 'int' et non 'int&&', et est une lvalue
        // std::forward<int>(i) a pour type 'int' et est une rvalue
    }
    Sinon, loufoque, tu confirmes que :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    struct T
    {
       T() = default;
       T(const T&) = delete;
    };
     
    f(T&& t)
    {
    ...
    }
     
    T t;
    f(t); // compile avec GCC du 14 février
    ne devrait pas compiler avec le draft actuel ?
    Je confirme que c'est tout à fait correct. En quoi ça ne le serait pas ? Parce qu'il faudrait convertir la lvalue en rvalue avec une copie, ce qui n'est pas possible ?
    Sauf que ça ne marche plus comme ça en C++0x...
    Boost ftw

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

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. move semantic et attributs const
    Par CedricMocquillon dans le forum C++
    Réponses: 12
    Dernier message: 13/09/2009, 20h34
  5. alter table move
    Par jokos2000 dans le forum Oracle
    Réponses: 7
    Dernier message: 15/06/2005, 13h30

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