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>
:
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 usarstd::for_each(begin, end, std::ref(fun));
Pasar argumentos como
std::reference_wrapper<T>
a una expresiónstd::bind()
es bastante común para unir argumentos por referencia en lugar de por valor.Cuando se usa
std::reference_wrapper<T>
constd::make_tuple()
el elemento de tupla correspondiente se convierte enT&
no enT
: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 dereference_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 otrareference_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.