63 votes

Puis-je typiquement / toujours utiliser std :: forward au lieu de std :: move?

J'ai été regarder Scott Meyers parler sur des Références Universelles de le C++ et au-Delà de 2012 de la conférence, et tout prend sens jusqu'à présent. Cependant, un membre de l'auditoire pose une question, à environ 50 minutes en qui je me demandais aussi sur. Meyers dit qu'il ne se soucie pas de la réponse, car elle est non-idiomatiques et serait stupide son esprit, mais je suis toujours intéressé.

Le code présenté est comme suit:

// Typical function bodies with overloading:
void doWork(const Widget& param)   // copy
{
  // ops and exprs using param
}
void doWork(Widget&& param)        // move
{
  // ops and exprs using std::move(param)
}

// Typical function implementations with universal reference:
template <typename T>
void doWork(T&& param)             // forward => copy and move
{
  // ops and exprs using std::forward<T>(param)
}

Le point étant que lorsque nous prenons une référence rvalue, nous savons que nous avons une rvalue, donc nous devrions std::move il pour préserver le fait que c'est une rvalue. Lorsque nous prenons une référence universelle (T&&T est déduit de type), nous voulons std::forward afin de préserver le fait qu'il peut avoir été une lvalue ou une rvalue.

Donc la question est: depuis std::forward conserve si la valeur passée dans la fonction était soit une lvalue ou une rvalue, et std::move tout simplement jette son argument à une rvalue, pourrait-on simplement utiliser std::forward partout? Serait - std::forward se comportent comme des std::move dans tous les cas où l'on utiliserait std::move, ou il y a des différences importantes dans le comportement qui sont manqués par Meyers généralisation?

Je ne dis pas que tout le monde devrait le faire, parce que, comme Meyers dit correctement, il est complètement non-idiomatique, mais est également une utilisation valide de std::move:

void doWork(Widget&& param)         // move
{
  // ops and exprs using std::forward<Widget>(param)
}

70voto

Kerrek SB Points 194696

Les deux sont très différents et complémentaires des outils.

  • std::move en déduit l'argument et sans condition crée une rvalue expression. Ce qui fait sens pour les appliquer à un objet ou une variable.

  • std::forward prend obligatoirement un argument de modèle (vous devez le spécifier!) et comme par magie crée une lvalue de référence ou une rvalue expression en fonction de ce que le type a (par l'effet de l'ajout d' && et l'effondrement des règles). Cela n'a de sens que pour les appliquer à une déduit, basé sur un modèle argument de fonction.

Peut-être les exemples suivants illustrent la présente un peu mieux:

#include <utility>
#include <memory>
#include <vector>
#include "foo.hpp"

std::vector<std::unique_ptr<Foo>> v;

template <typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));  // #1
}

int main()
{
    {
        std::unique_ptr<Foo> p(new Foo('a', true, Bar(1,2,3)));
        v.push_back(std::move(p));                                  // #2
    }

    {
        v.push_back(make_unique<Foo>('b', false, Bar(5,6,7)));      // #3
    }

    {
        Bar b(4,5,6);
        char c = 'x';
        v.push_back(make_unique<Foo>(c, b.ready(), b));             // #4
    }
}

Dans la situation 2, nous avons d'un objet concret p, et nous voulons nous déplacer, de, sans condition. Seulement std::move de sens. Il n'y a rien à "l'avant" ici. Nous avons un nom de variable et nous voulons aller.

D'autre part, de la situation #1 accepte une liste de toute sorte d'arguments, et chaque argument doit être transmis que la même valeur de la catégorie qu'il était dans l'appel d'origine. Par exemple, dans le n ° 3 les arguments sont des expressions temporaires, et donc, ils seront transférés rvalues. Mais on pourrait aussi se sont mélangés dans les objets nommés dans l'appel du constructeur, comme dans la situation n ° 4, et puis nous avons besoin de la redirection de lvalues.

14voto

Mankarse Points 22800

Oui, si param est Widget&&, puis les trois expressions suivantes sont équivalentes (en supposant que l' Widget n'est pas un type de référence):

std::move(param)
std::forward<Widget>(param)
static_cast<Widget&&>(param)

En général (quand Widget peut être une référence), std::move(param) est équivalente à la fois des expressions suivantes:

std::forward<std::remove_reference<Widget>::type>(param)
static_cast<std::remove_reference<Widget>::type&&>(param)

Remarquez comment beaucoup plus agréable std::move est pour transporter des choses. Le point de std::forward , c'est qu'il se mélange bien avec le type de modèle de règles de déduction:

template<typename T>
void foo(T&& t) {
    std::forward<T>(t);
    std::move(t);
}

int main() {
    int a{};
    int const b{};
               //Deduced T   Signature    Result of `forward<T>` Result of `move`
    foo(a);    //int&        foo(int&)       lvalue int          xvalue int
    foo(b);    //int const&  foo(int const&) lvalue int const    xvalue int const
    foo(int{});//int         foo(int&&)      xvalue int          xvalue int
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X