variable uso solo sirve significa que programación para imprimir guardar datos carácter almacenar c++ c++11 rvalue-reference c++-faq perfect-forwarding

uso - ¿Qué significa T&&(doble ampersand) en C++ 11?



uso de & en c++ (4)

He estado investigando algunas de las nuevas características de C ++ 11 y una de las que he notado es el doble y en la declaración de variables, como T&& var .

Para empezar, ¿cómo se llama esta bestia? Me gustaría que Google nos permitiera buscar puntuaciones como esta.

¿Qué significa exactamente ?

A primera vista, parece ser una doble referencia (como los punteros dobles de estilo C T** var ), pero me resulta difícil pensar en un caso de uso para eso.


Declara una referencia de valor (documento de propuesta de normas).

Aquí hay una introducción a las references .

Esta es una fantástica mirada en profundidad a las referencias de valores de uno de los developers bibliotecas estándar de Microsoft. (Pero vea la Precaución en los comentarios que siguen a esta respuesta antes de leer este artículo).

La mayor diferencia entre una referencia de C ++ 03 (ahora llamada referencia de valor l en C ++ 11) es que puede unirse a un valor de r como un temporal sin tener que ser constante. Por lo tanto, esta sintaxis es ahora legal:

T&& r = T();

Las referencias de valor proporcionan principalmente lo siguiente:

Mover la semántica . Ahora se pueden definir un constructor de movimiento y un operador de asignación de movimiento que toma una referencia de valor de r en lugar de la referencia de valor de const habitual. Un movimiento funciona como una copia, excepto que no está obligado a mantener la fuente sin cambios; de hecho, generalmente modifica la fuente de manera que ya no posee los recursos movidos. Esto es excelente para eliminar copias extrañas, especialmente en implementaciones de bibliotecas estándar.

Por ejemplo, un constructor de copia podría tener este aspecto:

foo(foo const& other) { this->length = other.length; this->ptr = new int[other.length]; copy(other.ptr, other.ptr + other.length, this->ptr); }

Si a este constructor se le pasara un temporal, la copia sería innecesaria porque sabemos que el temporal simplemente será destruido; ¿Por qué no hacer uso de los recursos que el temporal ya asignó? En C ++ 03, no hay forma de evitar la copia, ya que no podemos determinar que se nos haya pasado un mensaje temporal. En C ++ 11, podemos sobrecargar un constructor de movimiento:

foo(foo&& other) { this->length = other.length; this->ptr = other.ptr; other.length = 0; other.ptr = nullptr; }

Note la gran diferencia aquí: el constructor de movimiento realmente modifica su argumento. Esto efectivamente "movería" lo temporal al objeto que se está construyendo, eliminando así la copia innecesaria.

El constructor de movimientos se usaría para temporales y para referencias de valores no constantes que se convierten explícitamente en referencias de valores utilizando la función std::move (simplemente realiza la conversión). El siguiente código invoca el constructor de movimiento para f1 y f2 :

foo f1((foo())); // Move a temporary into f1; temporary becomes "empty" foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"

Reenvío perfecto . Las referencias de rvalue nos permiten enviar correctamente los argumentos para las funciones de plantilla. Tomemos por ejemplo esta función de fábrica:

template <typename T, typename A1> std::unique_ptr<T> factory(A1& a1) { return std::unique_ptr<T>(new T(a1)); }

Si llamamos factory<foo>(5) , se deducirá que el argumento es int& , que no se unirá a un literal 5, incluso si el constructor de foo toma un int . Bueno, podríamos usar A1 const& , pero, ¿y si foo toma el argumento del constructor por referencia no constante? Para realizar una función de fábrica realmente genérica, tendríamos que sobrecargar la fábrica en A1& y en A1 const& . Eso podría estar bien si la fábrica toma 1 tipo de parámetro, pero cada tipo de parámetro adicional multiplicaría la sobrecarga necesaria establecida por 2. Eso es muy rápidamente imposible de mantener.

Las referencias de rvalue solucionan este problema al permitir que la biblioteca estándar defina una función std::forward que puede reenviar correctamente las referencias de lvalue / rvalue. Para obtener más información sobre cómo funciona std::forward , vea esta excelente respuesta .

Esto nos permite definir la función de fábrica de esta manera:

template <typename T, typename A1> std::unique_ptr<T> factory(A1&& a1) { return std::unique_ptr<T>(new T(std::forward<A1>(a1))); }

Ahora el argumento rvalue / lvalue-ness se conserva cuando se pasa al constructor de T Eso significa que si se llama a factory con un rvalue, el constructor de T se llama con un rvalue. Si se llama a factory con un lvalue, el constructor de T se llama con un lvalue. La función de fábrica mejorada funciona debido a una regla especial:

Cuando el tipo de parámetro de la función es de la forma T&& donde T es un parámetro de plantilla, y el argumento de la función es un lvalor de tipo A , el tipo A& se usa para la deducción de los argumentos de la plantilla.

Por lo tanto, podemos utilizar la fábrica como tal:

auto p1 = factory<foo>(foo()); // calls foo(foo&&) auto p2 = factory<foo>(*p1); // calls foo(foo const&)

Propiedades importantes de referencia de rvalue :

  • Para la resolución de sobrecargas, los valores de l prefieren el enlace a las referencias de valor de l y los valores de r prefieren el enlace a las referencias de valor de r . Por lo tanto, ¿por qué los temporales prefieren invocar un constructor de movimiento / mover el operador de asignación sobre un constructor de copia / operador de asignación?
  • Las referencias de rvalue se unirán implícitamente a rvalues ​​y a temporales que son el resultado de una conversión implícita . es decir, float f = 0f; int&& i = f; float f = 0f; int&& i = f; está bien formado porque float es implícitamente convertible a int; La referencia sería a un temporal que es el resultado de la conversión.
  • Las referencias de valores nominales son valores de l. Las referencias sin valor de rvalue son rvalues. Esto es importante para comprender por qué es necesaria la llamada std::move en: foo&& r = foo(); foo f = std::move(r); foo&& r = foo(); foo f = std::move(r);

Denota una referencia rvalue. Las referencias de valor solo se vincularán a objetos temporales, a menos que se genere explícitamente lo contrario. Se utilizan para hacer que los objetos sean mucho más eficientes en ciertas circunstancias, y para proporcionar una instalación conocida como reenvío perfecto, que simplifica enormemente el código de la plantilla.

En C ++ 03, no puede distinguir entre una copia de un valor l no mutable y un valor r.

std::string s; std::string another(s); // calls std::string(const std::string&); std::string more(std::string(s)); // calls std::string(const std::string&);

En C ++ 0x, este no es el caso.

std::string s; std::string another(s); // calls std::string(const std::string&); std::string more(std::string(s)); // calls std::string(std::string&&);

Considere la implementación detrás de estos constructores. En el primer caso, la cadena debe realizar una copia para retener la semántica del valor, lo que implica una nueva asignación de almacenamiento dinámico. Sin embargo, en el segundo caso, sabemos de antemano que el objeto que se pasó a nuestro constructor se debe de inmediato a la destrucción, y no tiene que permanecer intacto. Podemos efectivamente intercambiar los punteros internos y no realizar ninguna copia en este escenario, que es sustancialmente más eficiente. La semántica de Move beneficia a cualquier clase que tenga una copia costosa o prohibida de los recursos de referencia interna. Considere el caso de std::unique_ptr : ahora que nuestra clase puede distinguir entre temporarios y no temporales, podemos hacer que la semántica de movimiento funcione correctamente de modo que el unique_ptr no pueda copiarse pero se pueda mover, lo que significa que std::unique_ptr puede estar legalmente almacenado en contenedores estándar, ordenados, etc., mientras que std::auto_ptr C ++ 03 no puede.

Ahora consideramos el otro uso de las referencias rvalue - reenvío perfecto. Considere la cuestión de vincular una referencia a una referencia.

std::string s; std::string& ref = s; (std::string&)& anotherref = ref; // usually expressed via template

No puedo recordar lo que C ++ 03 dice acerca de esto, pero en C ++ 0x, el tipo resultante al tratar con referencias de valor es crítico. Una referencia rvalue a un tipo T, donde T es un tipo de referencia, se convierte en una referencia de tipo T.

(std::string&)&& ref // ref is std::string& (const std::string&)&& ref // ref is const std::string& (std::string&&)&& ref // ref is std::string&& (const std::string&&)&& ref // ref is const std::string&&

Considere la función de plantilla más simple: mínimo y máximo. En C ++ 03 tienes que sobrecargar las cuatro combinaciones de const y non-const manualmente. En C ++ 0x es solo una sobrecarga. Combinado con las plantillas variadic, esto permite un reenvío perfecto.

template<typename A, typename B> auto min(A&& aref, B&& bref) { // for example, if you pass a const std::string& as first argument, // then A becomes const std::string& and by extension, aref becomes // const std::string&, completely maintaining it''s type information. if (std::forward<A>(aref) < std::forward<B>(bref)) return std::forward<A>(aref); else return std::forward<B>(bref); }

Dejé de lado la deducción del tipo de retorno, porque no puedo recordar cómo se hizo de antemano, pero ese mínimo puede aceptar cualquier combinación de valores, valores, valores constantes.


El término para T&& cuando se usa con deducción de tipo (como para un reenvío perfecto) se conoce coloquialmente como referencia de reenvío . El término "referencia universal" fue acuñado por Scott Meyers en este artículo , pero más tarde fue cambiado.

Esto se debe a que puede ser r-value o l-value.

Algunos ejemplos son:

// template template<class T> foo(T&& t) { ... } // auto auto&& t = ...; // typedef typedef ... T; T&& t = ...; // decltype decltype(...)&& t = ...;

Más discusión se puede encontrar en la respuesta para: Sintaxis para referencias universales


Una referencia rvalue es un tipo que se comporta de manera muy similar a la referencia ordinaria X &, con varias excepciones. El más importante es que cuando se trata de la resolución de sobrecarga de funciones, los valores de l prefieren las referencias de valores de estilo antiguo, mientras que los valores de r prefieren las nuevas referencias de valor de r:

void foo(X& x); // lvalue reference overload void foo(X&& x); // rvalue reference overload X x; X foobar(); foo(x); // argument is lvalue: calls foo(X&) foo(foobar()); // argument is rvalue: calls foo(X&&)

Entonces, ¿qué es un valor? Cualquier cosa que no sea un valor. Un lvalue es una expresión que se refiere a una ubicación de memoria y nos permite tomar la dirección de esa ubicación de memoria a través del operador &.

Es casi más fácil entender primero lo que rvalues ​​logra con un ejemplo:

class Sample { int *ptr; // large block of memory int size; public: Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz} {} // copy constructor that takes lvalue Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :/ nullptr}, size{s.size} { std::cout << "copy constructor called on lvalue/n"; } // move constructor that take rvalue Sample(Sample&& s) { // steal s''s resources ptr = s.ptr; size = s.size; s.ptr = nullptr; // destructive write s.size = 0; cout << "Move constructor called on rvalue." << std::endl; } // normal copy assignment operator taking lvalue Sample& operator=(const Sample& s) { if(this != &s) { delete [] ptr; // free current pointer ptr = new int[s.size]; size = s.size; } cout << "Copy Assignment called on lvalue." << std::endl; return *this; } // overloaded move assignment operator taking rvalue Sample& operator=(Sample&& lhs) { if(this != &s) { delete [] ptr; //don''t let ptr be orphaned ptr = lhs.ptr; //but now "steal" lhs, don''t clone it. size = lhs.size; lhs.ptr = nullptr; // lhs''s new "stolen" state lhs.size = 0; } cout << "Move Assignment called on rvalue" << std::endl; return *this; } //...snip };

El constructor y los operadores de asignación han sido sobrecargados con versiones que toman referencias de valor. Las referencias de valores permiten que una función se bifurque en tiempo de compilación (mediante resolución de sobrecarga) en la condición "¿Se me llama en un valor o valor?". Esto nos permitió crear constructores y operadores de asignación más eficientes que mover los recursos en lugar de copiarlos.

El compilador se bifurca automáticamente en el momento de la compilación (dependiendo de si se está invocando para un lvalue o un rvalue) y elige si se debe llamar al constructor de movimientos o al operador de asignación de movimientos.

Resumiendo: las referencias de valores permiten la semántica de movimientos (y el reenvío perfecto, que se explica en el enlace del artículo a continuación).

Un ejemplo práctico y fácil de entender es la plantilla de clase std :: unique_ptr . Dado que unique_ptr mantiene la propiedad exclusiva de su puntero en bruto subyacente, unique_ptr no se puede copiar. Eso violaría su invariante de propiedad exclusiva. Así que no tienen copia constructores. Pero sí tienen constructores de movimiento:

template<class T> class unique_ptr { //...snip unique_ptr(unique_ptr&& __u) noexcept; // move constructor }; std::unique_ptr<int[] pt1{new int[10]}; std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor. // So we must first cast ptr1 to an rvalue std::unique_ptr<int[]> ptr2{std::move(ptr1)}; std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,/ int size) { for (auto i = 0; i < size; ++i) { param[i] += 10; } return param; // implicitly calls unique_ptr(unique_ptr&&) } // Now use function unique_ptr<int[]> ptr{new int[10]}; // first cast ptr from lvalue to rvalue unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(/ static_cast<unique_ptr<int[]>&&>(ptr), 10); cout << "output:/n"; for(auto i = 0; i< 10; ++i) { cout << new_owner[i] << ", "; } output: 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,

static_cast<unique_ptr<int[]>&&>(ptr) usualmente se hace usando std :: move

// first cast ptr from lvalue to rvalue unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);

Un excelente artículo que explica todo esto y más (como la forma en que los valores permiten el reenvío perfecto y lo que eso significa) con muchos buenos ejemplos es la explicación de las referencias en valores en C ++ de Thomas Becker. Este post se basó en gran medida en su artículo.

Una introducción más corta es references de Rvalue por Stroutrup, et. Alabama