c++ c++11 move-semantics rvalue-reference perfect-forwarding

c++ - ¿Puedo normalmente/siempre usar std:: forward en lugar de std:: move?



c++11 move-semantics (2)

He estado viendo la charla de Scott Meyers sobre referencias universales de la conferencia C ++ y Beyond 2012, y todo tiene sentido hasta ahora. Sin embargo, un miembro de la audiencia hace una pregunta de unos 50 minutos en la que también me preguntaba. Meyers dice que no le importa la respuesta porque no es idiomática y sería tonto, pero aún estoy interesado.

El código presentado es el siguiente:

// 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) }

El punto es que cuando tomamos una referencia de valor real, sabemos que tenemos un valor r, así que debemos std::move para preservar el hecho de que es un valor r. Cuando tomamos una referencia universal ( T&& , donde T es un tipo deducido), queremos que std::forward preserve el hecho de que puede haber sido un valor l o un valor r.

Entonces la pregunta es: dado que std::forward preserva si el valor pasado a la función era un valor lval o un valor r, y std::move simplemente lanza su argumento a un valor r, ¿podríamos simplemente usar std::forward cualquier lugar? std::forward comportaría como std::move en todos los casos en que std::move , o hay algunas diferencias importantes en el comportamiento que se pierden por la generalización de Meyers?

No estoy sugiriendo que alguien deba hacerlo porque, como Meyers dice correctamente, es completamente no idiomático, pero es el siguiente también un uso válido de std::move :

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


Las dos son herramientas muy diferentes y complementarias .

  • std::move deduce el argumento y crea incondicionalmente una expresión rvalue. Esto tiene sentido para aplicar a un objeto o variable real.

  • std::forward toma un argumento de plantilla obligatorio (¡debes especificar esto!) y mágicamente crea una referencia lvalue o una expresión rvalue dependiendo de qué tipo fue (en virtud de agregar && y las reglas colapsadas). Esto solo tiene sentido para aplicar a un argumento de función deducido y con plantilla.

Quizás los siguientes ejemplos ilustran esto un poco mejor:

#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 } }

En la situación n. ° 2, tenemos un objeto concreto existente p , y queremos pasar de él, incondicionalmente. Solo std::move tiene sentido. No hay nada que "reenviar" aquí. Tenemos una variable con nombre y queremos pasar de ella.

Por otro lado, la situación n. ° 1 acepta una lista de cualquier tipo de argumentos, y cada argumento debe reenviarse como la misma categoría de valor que en la llamada original. Por ejemplo, en el n. ° 3, los argumentos son expresiones temporales y, por lo tanto, se enviarán como valores r. Pero también podríamos haber mezclado objetos nombrados en la llamada de constructor, como en la situación n. ° 4, y luego necesitamos reenviar como valores l.


Sí, si param es un Widget&& , entonces las siguientes tres expresiones son equivalentes (suponiendo que Widget no sea un tipo de referencia):

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

En general (cuando Widget puede ser una referencia), std::move(param) es equivalente a las dos expresiones siguientes:

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

Tenga en cuenta cuánto más std::move es más agradable para mover cosas. El objetivo de std::forward es que se mezcle bien con las reglas de deducción del tipo de plantilla:

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 }