Partage
  • Partager sur Facebook
  • Partager sur Twitter

std::forward, copie ou déplacement

Sujet résolu
    23 août 2022 à 10:01:18

    Bonjour.

    Je suis en train de lire "Effective modern c++" de Scott Meyers. Je suis sur la partie std::move std::forward. Si je comprends bien std::move fait une conversion systématique en rvalue quand std::forward le fait de façon conditionnelle (si référence à une rvalue).

    Intéressons nous au std::forward. Deux exemples :

    1)

    #include <iostream>
    #include <vector>
    #include <utility>
    
    class test1 {
    public:
        test1() = default;
        
        test1(test1&&) {
            std::cout << "move ctor" << std::endl;
        }
        
        test1(const test1&) {
            std::cout << "copy ctor" << std::endl;
        }
    };
    
    int main()
    {
        std::vector<test1> vec1 { test1(), test1()};
        for(auto&& value : vec1) {
            std::cout << "for range" << std::endl;
            test1 forTest1 = std::forward<decltype(value)>(value);
        }
        return 0;
    } 

    Nous avons ici une copie car on fait référence à une lvalue. Je suis d'accord.

    2)

    #include <iostream>
    #include <vector>
    #include <utility>
    
    class test1 {
    public:
        test1() = default;
        
        test1(test1&&) {
            std::cout << "move ctor" << std::endl;
        }
        
        test1(const test1&) {
            std::cout << "copy ctor" << std::endl;
        }
    };
    
    int main()
    {
        for(auto&& value : std::vector { test1(), test1() }) {
            std::cout << "for range" << std::endl;
            test1 forTest1 = std::forward<decltype(value)>(value);
        }
        return 0;
    } 


    Ici je m'attends à avoir un déplacement or j'ai encore une copie. Et c'est cela que je ne comprend pas. Pourriez-vous m'éclairer s'il vous plait ?

    Merci.

    -
    Edité par Didy7 23 août 2022 à 10:02:07

    • Partager sur Facebook
    • Partager sur Twitter
      23 août 2022 à 11:56:04

      Bonjour,

      Le but du forward<> est de garder les caractéristiques initiale d'un paramètre pour pouvoir décider si un transfert est utilisable plutôt qu'une copie.
      Mais ici : auto&& value crée une l-value value (rappel tout ce qui a un nom est une l-value) qui l-référence une donnée du vector<test1> (ça n'est pas une r-référence car les éléments du vector<> sont aussi des l-value).

      On a donc une double raison que le forward<> va faire ce qui est compatible avec une l-value qui est forcément une copie.

      Correctif: le vector<> est une x-value et donc ses éléments sont des x-value, on devrait donc bien obtenir une r-référence dans value. Mais value reste une l-value et donc la copie est okay selon moi.

      -
      Edité par Dalfab 23 août 2022 à 12:04:35

      • Partager sur Facebook
      • Partager sur Twitter

      En recherche d'emploi.

        23 août 2022 à 14:25:52

        Dalfab a écrit:

        Bonjour,

        Le but du forward<> est de garder les caractéristiques initiale d'un paramètre pour pouvoir décider si un transfert est utilisable plutôt qu'une copie.
        Mais ici : auto&& value crée une l-value value (rappel tout ce qui a un nom est une l-value) qui l-référence une donnée du vector<test1> (ça n'est pas une r-référence car les éléments du vector<> sont aussi des l-value).

        On a donc une double raison que le forward<> va faire ce qui est compatible avec une l-value qui est forcément une copie.

        Correctif: le vector<> est une x-value et donc ses éléments sont des x-value, on devrait donc bien obtenir une r-référence dans value. Mais value reste une l-value et donc la copie est okay selon moi.

        -
        Edité par Dalfab il y a environ 1 heure


        Ok, donc à la ligne :

        for(auto&& value : std::vector { test1(), test1() }) 


        value n'est pas une l-value (car identifiable en mémoire) qui fait référence à une rvalue ?

        Le fait que value soit une r-référence ne fait pas que le move ctor soit systématiquement appelé par le biais de std::forward ?

        Il y a peut-être quelque chose qui m'échappe ou une lacune à combler...

        Merci !

        Merci pour ta réponse.

        -
        Edité par Didy7 23 août 2022 à 14:33:26

        • Partager sur Facebook
        • Partager sur Twitter
          23 août 2022 à 21:46:47

          Utilisé avec auto, les mécanismes derrières && ne sont pas les mêmes qu'en spécifiant le type. Par contre ce sont les mêmes qu'avec les templates.

          auto&& est soit une l-value, soit une r-value en fonction de l'expression qui va l'initialisée. Pour un for, il faut regarder ce qu'il fait réellement (le code d'équivalence): https://en.cppreference.com/w/cpp/language/range-for

          value est initialisée avec le déréférencement de l'itérateur: auto&& value = *it. Comme c'est un itérateur sur un vector de test1 et qu'il n'est pas const, il retourne un test1& (donc une l-value). D'après la règle de reference collapsing, combiner une l-value avec une r-value donne une l-value.

          Par conséquent value est une l-value. S'il y avait test1&& value, le compilateur refuserait de compiler puisqu'une référence ne pas se mettre dans une rvalue.

          Concernant l'histoire du déplacement, cela ne s'applique que sur des types pleins (des types sans référence). Mettre une valeur dans une référence ne fait aucun déplacement et par extension n'utilise aucun constructeur.

          -
          Edité par jo_link_noir 23 août 2022 à 21:47:27

          • Partager sur Facebook
          • Partager sur Twitter
            23 août 2022 à 23:03:55

            Dalfab a écrit:

            Le but du forward<> est de garder les caractéristiques initiale d'un paramètre pour pouvoir décider si un transfert est utilisable plutôt qu'une copie.

            Mais ici : auto&& value crée une l-value value (rappel tout ce qui a un nom est une l-value) qui l-référence une donnée du vector<test1> (ça n'est pas une r-référence car les éléments du vector<> sont aussi des l-value).

            On a donc une double raison que le forward<> va faire ce qui est compatible avec une l-value qui est forcément une copie.

            Correctif: le vector<> est une x-value et donc ses éléments sont des x-value, on devrait donc bien obtenir une r-référence dans value. Mais value reste une l-value et donc la copie est okay selon moi.

            J'ai un peu écrit n'importe quoi.

            Le forward<> est à utiliser sur un paramètre reçu par forward référence, ou bien sur une variable définie avec auto&& ce qui revient au même. On est bien dans ce cas. Le forward<> va alors faire son boulot en appliquant un std::move() si la donnée récupérée est effectivement une r-value.

            Mais jo_link_noir l'a bien écrit, la boucle for-each est décodée en prenant un itérateur sur l'objet temporaire, mais à cet instant on perd le fait que le vector<> est temporaire, on a un std::vector<>::iterator, pas un std::move_iterator{std::vector<>::iterator}. Le code n'est pas équivalent à:

                auto&&  v{std::vector<test1>{test1(), test1()}};
                for ( auto it = std::make_move_iterator(begin(v)) ; it != std::make_move_iterator(end(v)) ; ++it ) {
                    auto&&  value = *it;                   // ici value est bien une test1&&
                    std::cout << "for range" << std::endl;
                    test1 forTest1 = std::forward<decltype(value)>( value );
                }

            Avec make_move_iterator() on a bien un transfert, mais le code équivalent n'a pas cette fonction. 
            Le vector<> initial est bien un temporaire, la norme ne semble pas avoir pris ça en compte pour choisir un itérateur qui utilise cette caractéristique.

            • Partager sur Facebook
            • Partager sur Twitter

            En recherche d'emploi.

              1 septembre 2022 à 20:08:15

              jo_link_noir a écrit:

              Utilisé avec auto, les mécanismes derrières && ne sont pas les mêmes qu'en spécifiant le type. Par contre ce sont les mêmes qu'avec les templates.

              auto&& est soit une l-value, soit une r-value en fonction de l'expression qui va l'initialisée. Pour un for, il faut regarder ce qu'il fait réellement (le code d'équivalence): https://en.cppreference.com/w/cpp/language/range-for

              value est initialisée avec le déréférencement de l'itérateur: auto&& value = *it. Comme c'est un itérateur sur un vector de test1 et qu'il n'est pas const, il retourne un test1& (donc une l-value). D'après la règle de reference collapsing, combiner une l-value avec une r-value donne une l-value.

              Par conséquent value est une l-value. S'il y avait test1&& value, le compilateur refuserait de compiler puisqu'une référence ne pas se mettre dans une rvalue.

              Concernant l'histoire du déplacement, cela ne s'applique que sur des types pleins (des types sans référence). Mettre une valeur dans une référence ne fait aucun déplacement et par extension n'utilise aucun constructeur.

              -
              Edité par jo_link_noir 23 août 2022 à 21:47:27


              Effectivement si je ne dis pas trop de bétise, déduction de type + && = référence universelle et l'utilisation de std::forward se nomme la transmission parfaite. Merci pour ton lien je comprends mieux pourquoi il n'y a pas de déplacement.

              Je cloture en laissant libre les gens de rebondir. Merci à vous deux !

              • Partager sur Facebook
              • Partager sur Twitter

              std::forward, copie ou déplacement

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
              • Editeur
              • Markdown