Saltar al contenido

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

Solución:

Declara una referencia rvalue (propuesta de estándares doc).

Aquí hay una introducción a las referencias de rvalue.

Aquí hay una fantástica mirada en profundidad a las referencias de rvalue por uno de los desarrolladores de bibliotecas estándar de Microsoft.

PRECAUCIÓN: el artículo vinculado sobre MSDN (“Referencias de Rvalue: Características de C ++ 0x en VC10, Parte 2”) es una introducción muy clara a las referencias de Rvalue, pero hace declaraciones sobre las referencias de Rvalue que alguna vez fueron verdaderas en el borrador del estándar C ++ 11, ¡pero no son ciertas para el final! Específicamente, dice en varios puntos que las referencias de rvalue pueden unirse a lvalues, lo que alguna vez fue verdadero, pero se cambió (por ejemplo, int x; int && rrx = x; ya no se compila en GCC) – drewbarbs 13 de julio de 14 a las 16:12

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

T&& r = T();

Las referencias de rvalue proporcionan principalmente lo siguiente:

Mover semántica. Ahora se puede definir un constructor de movimiento y un operador de asignación de movimiento que tome una referencia rvalue en lugar de la referencia habitual const-lvalue. Un movimiento funciona como una copia, excepto que no está obligado a mantener la fuente sin cambios; de hecho, normalmente modifica la fuente de modo que ya no sea el propietario de los recursos movidos. Esto es excelente para eliminar copias superfluas, especialmente en implementaciones de bibliotecas estándar.

Por ejemplo, un constructor de copias podría verse así:

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 pasó un temporal, la copia sería innecesaria porque sabemos que el temporal simplemente se destruirá; ¿Por qué no hacer uso de los recursos temporales ya asignados? En C ++ 03, no hay forma de evitar la copia, ya que no podemos determinar que se nos pasó un archivo temporal. En C ++ 11, podemos sobrecargar un constructor de movimientos:

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

Observe la gran diferencia aquí: el constructor de movimientos en realidad modifica su argumento. Esto efectivamente “movería” lo temporal al objeto que se está construyendo, eliminando así la copia innecesaria.

El constructor de movimiento se usaría para temporales y para referencias no constantes de lvalue que se convierten explícitamente en referencias de rvalue usando el std::move función (solo realiza la conversión). El siguiente código invoca al 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 reenviar correctamente argumentos para funciones con 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 vinculará a un literal 5, incluso si fooEl constructor toma un int. Bueno, en su lugar podríamos usar A1 const&, pero que si foo toma el argumento del constructor por referencia no constante? Para hacer una función de fábrica verdaderamente 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 inmantenible.

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

Esto nos permite definir la función de fábrica así:

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 rvalue / lvalue-ness del argumento se conserva cuando se pasa a Tconstructor de. Eso significa que si se llama a factory con un rvalue, TEl constructor de se llama con un rvalue. Si se llama a la fábrica con un lvalue, TEl constructor de 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 función tiene la forma T&& dónde T es un parámetro de plantilla y el argumento de la función es un lvalue de tipo A, el tipo A& se utiliza para la deducción de argumentos de plantilla.

Por lo tanto, podemos usar factory así:

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

Propiedades de referencia importantes de rvalue:

  • Para la resolución de sobrecarga, lvalues ​​prefieren vincularse a referencias de lvalue y rvalues ​​prefieren vincularse a referencias de rvalue. De ahí por qué los temporales prefieren invocar un constructor de movimiento / operador de asignación de movimiento sobre un constructor de copia / operador de asignación.
  • Las referencias de rvalue se vincularán implícitamente a rvalues ​​y a los temporales que son el resultado de una conversión implícita. es decir 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 rvalue con nombre son lvalues. Las referencias de rvalue sin nombre son rvalues. Esto es importante para comprender por qué std::move la llamada es necesaria en: foo&& r = foo(); foo f = std::move(r);

Denota una referencia rvalue. Las referencias de Rvalue solo se vincularán a objetos temporales, a menos que se generen explícitamente de otra manera. Se utilizan para hacer que los objetos sean mucho más eficientes en determinadas circunstancias y para proporcionar una función conocida como reenvío perfecto, que simplifica enormemente el código de plantilla.

En C ++ 03, no se 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 tiene que realizar una copia para retener la semántica del valor, lo que implica una nueva asignación de montón. Sin embargo, en el segundo caso, sabemos de antemano que el objeto que se pasó a nuestro constructor debe ser destruido inmediatamente y no tiene que permanecer intacto. Efectivamente, podemos simplemente intercambiar los punteros internos y no realizar ninguna copia en este escenario, que es sustancialmente más eficiente. La semántica de movimiento beneficia a cualquier clase que tenga costosa o prohibida la copia de recursos referenciados internamente. Considere el caso de std::unique_ptr– ahora que nuestra clase puede distinguir entre temporales y no temporales, podemos hacer que la semántica de movimientos funcione correctamente para que el unique_ptr no se puede copiar pero se puede mover, lo que significa que std::unique_ptr puede almacenarse legalmente en contenedores estándar, ordenados, etc., mientras que C ++ 03 std::auto_ptr no poder.

Ahora consideramos el otro uso de las referencias rvalue: el 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 dice C ++ 03 sobre esto, pero en C ++ 0x, el tipo resultante cuando se trata de referencias rvalue 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 no const manualmente. En C ++ 0x es solo una sobrecarga. Combinado con plantillas variadas, 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é la deducción del tipo de retorno, porque no puedo recordar cómo se hace de inmediato, pero ese mínimo puede aceptar cualquier combinación de lvalues, rvalues, const lvalues.

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

Esto se debe a que puede ser un valor r o un valor l.

Algunos ejemplos son:

// template
template<class T> foo(T&& t) { ... }

// auto
auto&& t = ...;

// typedef
typedef ... T;
T&& t = ...;

// decltype
decltype(...)&& t = ...;

Se puede encontrar más discusión en la respuesta para: Sintaxis para referencias universales

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *