What is rvalue reference declarator?

Guía completa de std::ref y cref en C++

23/06/2009

Valoración: 4.44 (9846 votos)

En el vasto universo de C++, el manejo de referencias es una de las herramientas más fundamentales y potentes que un desarrollador puede dominar. Nos permiten crear alias para objetos existentes, evitando copias costosas y permitiendo la modificación directa de variables en diferentes ámbitos. Sin embargo, hay escenarios, especialmente en la programación genérica y con la Biblioteca de Plantillas Estándar (STL), donde las referencias (`&`) no se comportan como uno esperaría. Es en este punto donde entran en juego dos utilidades cruciales del C++ moderno: `std::ref` y su contraparte constante, `std::cref`. Este artículo es una inmersión profunda en qué son, por qué existen y cómo pueden salvarte de errores sutiles y mejorar la expresividad de tu código.

What is a ref in JavaScript?
A ref is defined as any value that does not trigger a component re-render when it is changed. This behavior is contrary to the function of states and props. A ref can be created in two ways- by the useRef hook or by the createRef function. The useRef is a hook that uses the same ref throughout.
Índice de Contenido

Un Repaso Rápido: ¿Qué es una Referencia en C++?

Antes de sumergirnos en `std::ref`, recordemos brevemente qué es una referencia estándar en C++. Una referencia, declarada con el ampersand (`&`), es esencialmente un nombre alternativo para un objeto ya existente. No es un puntero; no puede ser nula, no puede ser reasignada para referenciar a otro objeto después de su inicialización y no requiere un operador de desreferenciación para acceder a su valor.

Su uso más común es en los parámetros de funciones para implementar el paso por referencia:

void incrementar(int& valor) { valor++; // Modifica directamente el objeto original}int main() { int miNumero = 10; incrementar(miNumero); // Ahora, miNumero es 11}

Este mecanismo es eficiente y claro. Sin embargo, la simplicidad de las referencias estándar encuentra un obstáculo cuando interactúan con las plantillas de funciones y la semántica de copia.

What does the ref keyword mean in C#?
In C#, the ref keyword is used to pass arguments by reference and to define reference parameters. Using ref can be useful in certain situations where you need to modify the value of a variable inside a method and have that change reflected in the calling method.

El Problema: La Decadencia de las Referencias

Muchas funciones de plantilla en C++, como `std::bind`, `std::thread` o los algoritmos de la STL, están diseñadas para tomar sus argumentos por valor. Esto significa que, por defecto, realizan una copia de lo que se les pasa. Cuando pasas una variable a una de estas funciones, su tipo "decae": las referencias se eliminan y se crea una copia del objeto subyacente.

Consideremos `std::bind`, una utilidad que nos permite crear un objeto de función (un "functor") con algunos o todos los argumentos de una función ya predefinidos.

What are function templates ref & CREF?
Function templates ref and cref are helper functions that generate an object of type std::reference_wrapper, using template argument deduction to determine the template argument of the result. T may be an incomplete type. 3,6) rvalue reference wrapper is deleted. n1 = 10; n2 = 11;
#include <iostream>#include <functional>void mi_funcion(int& n1, int& n2) { std::cout << "Dentro de la función: n1=" << n1 << ", n2=" << n2 << std::endl; n1++; n2++;}int main() { int a = 1, b = 2; auto funcion_enlazada = std::bind(mi_funcion, a, b); std::cout << "Antes de llamar: a=" << a << ", b=" << b << std::endl; funcion_enlazada(); std::cout << "Después de llamar: a=" << a << ", b=" << b << std::endl;}

Si ejecutas este código, la salida podría sorprenderte:

Antes de llamar: a=1, b=2Dentro de la función: n1=1, n2=2Después de llamar: a=1, b=2

¿Qué ha pasado? A pesar de que `mi_funcion` espera referencias (`int&`), `std::bind` copió los valores de `a` y `b` en el momento de su creación. La función `funcion_enlazada` opera sobre esas copias internas, no sobre las variables originales `a` y `b`. Este comportamiento, aunque seguro por defecto, frustra nuestro intento de modificar las variables originales. Aquí es exactamente donde `std::ref` se convierte en nuestro mejor aliado.

La Solución: `std::reference_wrapper` y `std::ref`

Para resolver este problema, C++11 introdujo `std::reference_wrapper`. Como su nombre indica, es una clase plantilla que "envuelve" una referencia en un objeto. Lo más importante es que un objeto `std::reference_wrapper` es copiable y asignable, a diferencia de una referencia nativa (`T&`).

Cuando una función de plantilla como `std::bind` recibe un `std::reference_wrapper`, sabe que la intención del programador es mantener la semántica de referencia. En lugar de copiar el objeto envuelto, copia el `wrapper`, que sigue apuntando al objeto original.

What does 'ref' mean in C++?
In C++, the meaning of 'ref' depends on the type. For value types, your assessment is correct (or close enough). For reference types, a more suitable analogy would be a reference-to-pointer: The whole point about C++'s 'ref' is that you can change the reference being passed in. And you can't do that in C++ by simply passing a pointer.

Crear manualmente un `std::reference_wrapper` es un poco verboso:

int a = 1;std::reference_wrapper<int> ref_a(a);

Para facilitar esto, la biblioteca estándar nos proporciona las funciones de ayuda `std::ref` y `std::cref`:

  • `std::ref(variable)`: Crea un `std::reference_wrapper` para la `variable`.
  • `std::cref(variable)`: Crea un `std::reference_wrapper` para la `variable`, asegurando que no pueda ser modificada a través de la referencia.

Ahora, reescribamos nuestro ejemplo anterior usando `std::ref`:

#include <iostream>#include <functional>void mi_funcion(int& n1, int& n2) { std::cout << "Dentro de la función: n1=" << n1 << ", n2=" << n2 << std::endl; n1++; n2++;}int main() { int a = 1, b = 2; // Usamos std::ref para pasar 'a' y 'b' por referencia auto funcion_enlazada = std::bind(mi_funcion, std::ref(a), std::ref(b)); std::cout << "Antes de llamar: a=" << a << ", b=" << b << std::endl; funcion_enlazada(); std::cout << "Después de llamar: a=" << a << ", b=" << b << std::endl;}

La salida ahora es la que esperábamos:

Antes de llamar: a=1, b=2Dentro de la función: n1=1, n2=2Después de llamar: a=2, b=3

¡Éxito! Al envolver nuestras variables con `std::ref`, le hemos indicado explícitamente a `std::bind` que preserve la referencia y no haga una copia del valor.

Casos de Uso Comunes

1. Con `std::thread`

Al igual que `std::bind`, el constructor de `std::thread` también copia sus argumentos. Si necesitas que un nuevo hilo opere sobre datos compartidos sin copiarlos, `std::ref` es esencial.

What is STD ref in C++?
When working with C++ standard containers and functions, handling references can sometimes lead to unexpected behavior, particularly with copy semantics. This is where std::ref and std::cref come into play, allowing you to store references in containers and pass them safely to template functions like std::bind or std::thread. What is std::ref?
void procesar_datos(std::vector<int>& data) { // Procesa el vector de datos. Cualquier cambio aquí // se reflejará en el vector original. for (int& val: data) { val *= 2; }}int main() { std::vector<int> mis_datos = {1, 2, 3, 4, 5}; // Pasamos el vector por referencia al nuevo hilo std::thread t(procesar_datos, std::ref(mis_datos)); t.join(); // Esperamos a que el hilo termine // mis_datos ahora contiene {2, 4, 6, 8, 10}}

2. Almacenar Referencias en Contenedores

Los contenedores estándar de la STL, como `std::vector`, requieren que sus elementos sean asignables y copiables. Una referencia nativa (`T&`) no cumple con estos requisitos, por lo que no puedes tener un `std::vector`. Sin embargo, sí puedes tener un `std::vector>`.

std::vector<std::string> nombres = {"Ana", "Luis", "Marta"};std::vector<std::reference_wrapper<std::string>> ref_nombres;for (std::string& nombre: nombres) { ref_nombres.push_back(std::ref(nombre));}// Ahora podemos modificar los nombres originales a través del vector de referenciasref_nombres[0].get() += " Pérez"; // nombres[0] ahora es "Ana Pérez"

Nota el uso de `.get()` para acceder al objeto referenciado desde el `reference_wrapper`.

Comparativa: Referencias, Punteros y `std::ref`

Para aclarar las diferencias, aquí tienes una tabla comparativa:

CaracterísticaReferencia Nativa (`&`)Puntero (`*`)`std::reference_wrapper`
¿Puede ser nulo?NoSí (`nullptr`)No (debe inicializarse con un objeto)
¿Se puede reasignar?No, se enlaza una vezSí, puede apuntar a otros objetosSí, el wrapper es reasignable
Sintaxis de AccesoDirecta (ej: `var`)Desreferenciación (ej: `*ptr`)Conversión implícita o `.get()`
Semántica en PlantillasSuele decaer a una copiaSe copia el puntero (la dirección)Mantiene la semántica de copia de referencia
Uso en Contenedores STLNo permitidoPermitido (ej: `std::vector<int*>`)Permitido (ej: `std::vector<std::ref<int>>`)

Preguntas Frecuentes (FAQ)

¿Es `std::ref` lo mismo que el operador `&` para obtener una dirección?
No, en absoluto. El operador `&` (cuando se usa en una expresión, no en una declaración) devuelve la dirección de memoria de un objeto, es decir, un puntero. `std::ref` es una función plantilla que devuelve un objeto `std::reference_wrapper`, que es un mecanismo completamente diferente para preservar la semántica de referencia.
¿Usar `std::ref` tiene un impacto en el rendimiento?
El impacto es prácticamente nulo. `std::reference_wrapper` es una envoltura muy delgada, a menudo del tamaño de un puntero. Es "trivially copyable", lo que significa que el compilador puede optimizar su copia de manera muy eficiente, a menudo a nivel de bytes. El costo es despreciable en comparación con la copia de un objeto complejo que se evita.
¿Por qué no usar simplemente un puntero?
Aunque pasar un puntero es una alternativa válida para lograr un efecto similar, `std::ref` a menudo expresa la intención de una manera más clara y segura. Una referencia (envuelta o no) transmite la idea de que "siempre se refiere a un objeto válido", mientras que un puntero introduce la posibilidad de ser nulo (`nullptr`), lo que requiere comprobaciones adicionales. Además, muchas APIs de la STL están diseñadas para funcionar de forma más idiomática con `reference_wrapper`.
¿Qué sucede si el objeto original es destruido?
Este es un punto crucial de seguridad. Al igual que con las referencias nativas y los punteros, si el objeto al que apunta un `std::reference_wrapper` es destruido, el `wrapper` se convierte en una "referencia colgante". Intentar acceder a su valor resultará en un comportamiento indefinido, que es uno de los errores más peligrosos en C++. El programador es siempre responsable de garantizar que el tiempo de vida de la referencia no exceda el tiempo de vida del objeto referenciado.

Conclusión

`std::ref` no es un sustituto de las referencias estándar, sino una herramienta especializada y poderosa para un problema específico: forzar la semántica de referencia en contextos de programación genérica donde la semántica de copia es la norma. Al comprender el problema de la decadencia de tipos y el papel de `std::reference_wrapper`, puedes escribir un código C++ más robusto, expresivo y eficiente. La próxima vez que trabajes con `std::bind`, `std::thread` o necesites un contenedor de referencias, recuerda que `std::ref` es la herramienta perfecta para el trabajo.

Si quieres conocer otros artículos parecidos a Guía completa de std::ref y cref en C++ puedes visitar la categoría Juegos.

Subir