c++ pointers c++11 reference reference-wrapper

c++ - Diferencia entre std:: reference_wrapper y simple puntero?



pointers c++11 (4)

Hay, al menos, dos propósitos motivadores de std::reference_wrapper<T> :

  1. Es para dar semántica de referencia a los objetos pasados ​​como parámetro de valor para plantillas de función. Por ejemplo, puede tener un objeto de función grande que desee pasar a std::for_each() que toma su parámetro de objeto de función por valor. Para evitar copiar el objeto, puede usar

    std::for_each(begin, end, std::ref(fun));

    Pasar argumentos como std::reference_wrapper<T> a una expresión std::bind() es bastante común para unir argumentos por referencia en lugar de por valor.

  2. Cuando se usa std::reference_wrapper<T> con std::make_tuple() el elemento de tupla correspondiente se convierte en T& no en T :

    T object; f(std::make_tuple(1, std::ref(object)));

¿Por qué es necesario tener std::reference_wrapper ? ¿Dónde debería usarse? ¿Cómo es diferente de un simple puntero? ¿Cómo se compara su rendimiento con un simple puntero?


Otra diferencia, en términos de código de auto-documentación, es que el uso de un reference_wrapper niega esencialmente la propiedad del objeto. Por el contrario, un unique_ptr afirma la propiedad, mientras que un puntero descubierto puede o no ser propiedad (no es posible saber sin mirar muchos códigos relacionados):

vector<int*> a; // the int values might or might not be owned vector<unique_ptr<int>> b; // the int values are definitely owned vector<reference_wrapper<int>> c; // the int values are definitely not owned


Puede considerarlo como un envoltorio de conveniencia alrededor de referencias para que pueda usarlas en contenedores.

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want std::vector<T&> vec2; // Nope! Will not compile

Básicamente es una versión CopyAssignable de T& . Cada vez que desee una referencia, pero tiene que ser asignable, use std::reference_wrapper<T> o su función auxiliar std::ref() . O usa un puntero.

Dado que agrega una capa adicional de direccionamiento indirecto, será estrictamente peor (pero probablemente insignificante) que T& en los casos en que ambos puedan usarse, por lo que solo debe usarlo cuando realmente lo necesite.

Otras peculiaridades:

sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24


std::reference_wrapper es útil en combinación con plantillas. Envuelve un objeto al guardar un puntero, lo que permite la reasignación y copia mientras imita su semántica habitual. También instruye a ciertas plantillas de biblioteca para almacenar referencias en lugar de objetos.

Considere los algoritmos en el STL que copian los funtores: puede evitar esa copia simplemente pasando un contenedor de referencia refiriéndose al functor en lugar del propio functor:

unsigned arr[10]; std::mt19937 myEngine; std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine''s state

Esto funciona porque ...

  • ... operator() sobrecarga de reference_wrapper operator() para que puedan ser llamados como los objetos de función a los que se refieren:

    std::ref(myEngine)() // Valid expression, modifies myEngines state

  • ... (un) como referencias ordinarias, copiando (y asignando) wrappers de reference_wrappers simplemente asigna el punto.

    int i, j; auto r = std::ref(i); // r refers to i r = std::ref(j); // Okay; r refers to j r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>

Copiar un contenedor de referencia es prácticamente equivalente a copiar un puntero, que es lo más barato posible. Todas las llamadas de funciones inherentes a su uso (por ejemplo, las de operator() ) deberían estar simplemente en línea, ya que son líneas rectas.

reference_wrapper s se crean a través de std::ref y std::cref :

int i; auto r = std::ref(i); // r is of type std::reference_wrapper<int> auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>

El argumento de la plantilla especifica el tipo y la calificación cv del objeto al que se hace referencia; r2 refiere a un const int y solo producirá una referencia a const int . Las llamadas a wrappers de referencia con funtores const solo llamarán al operator() función miembro operator() s.

Los inicializadores Rvalue no están permitidos, ya que permitirlos haría más daño que bien. Como los valores r se moverían de todos modos (y con la elisión de copia garantizada, incluso eso se evita en parte), no mejoramos la semántica; sin embargo, podemos introducir punteros colgantes, ya que una envoltura de referencia no extiende la vida útil del ponente.

Interacción de la biblioteca

Como se mencionó anteriormente, se puede indicar a make_tuple que almacene una referencia en la tuple resultante al pasar el argumento correspondiente a través de un reference_wrapper :

int i; auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int> auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i. // Type of t2 is tuple<int&>

Tenga en cuenta que esto difiere ligeramente de forward_as_tuple : Aquí, rvalues ​​como argumentos no están permitidos.

std::bind muestra el mismo comportamiento: no copiará el argumento, sino que almacenará una referencia si es un reference_wrapper . Útil si ese argumento (¡o el functor!) No necesita ser copiado sino que permanece en el alcance mientras se usa bind -functor.

Diferencia de punteros ordinarios

  • No hay un nivel adicional de indirección sintáctica. Los punteros deben desreferenciarse para obtener un valor l para el objeto al que se refieren; reference_wrapper s tiene un operador de conversión implícito y se puede llamar como el objeto que envuelven.

    int i; int& ref = std::ref(i); // Okay

  • reference_wrapper s, a diferencia de los punteros, no tienen un estado nulo. Deben inicializarse con una referencia u otra reference_wrapper .

    std::reference_wrapper<int> r; // Invalid

  • Una similitud es la semántica de la copia superficial: los punteros y las etiquetas de reference_wrapper pueden reasignarse.