Referencias a referencias
¿Es posible en C++ declarar una referencia a referencia? Y si se puede, ¿qué significa?
A primera vista parece que no se puede, el siguiente código provoca un error de sintaxis:
1 2 | int x; int & & t; |
El compilador g++ emite los siguientes errores (un tanto redundantes, por cierto):
2: error: no se puede declarar la referencia a ‘int&’, el cual no es una definición de tipo o un argumento de tipo de plantilla 2: error: no se puede declarar la referencia a ‘int&’
Pero podemos intentarlo de forma un poco más indirecta, con templates:
1 2 3 4 5 6 7 | template<typename T> struct ref { typedef T &type; }; //... int x; ref<int&>::type r = x; |
En la línea 7, x es simplemente una “referencia a int” o “int &”, porque se produce el llamado colapso de referencias.
Es curioso que en muchos casos estas “dobles referencias” son detectadas por el compilador y marcadas como errores. Por ejemplo:
int x; typedef int &ref; typedef ref &rref; //error ref &r = x; //error
Para que se compile el colapso con éxito es necesario que el compilador no vea nunca que sintácticamente se está creando una doble referencia.
En el ejemplo anterior, la referencia de línea 3 es válida porque es una referencia a T, pero no sabe qué tipo es ese. Y en la línea 7 ni siquiera se declara una referencia en sí, sino que instancia un cierto template con un tipo referencia. Si la instanciación de este template resulta en que el typedef acaba siendo una referencia a una referencia, entonces ocurre el colapso.
Referencias y punteros
Todos sabemos que se pueden definir punteros a punteros, y punteros a punteros a punteros, etc. hasta un nivel arbitrario. Pero ¿se pueden definir punteros a referencias? ¿Y referencias a punteros?
Las respuestas son “no” y “sí”.
Un puntero a una referencia no tiene sentido, porque una referencia no tiene necesariamente almacenamiento propio. Al intentar obtener la dirección de una referencia, en realidad se obtiene la del objeto vinculado, que es un puntero ordinario:
int x; int &r = x; int *p = &r; //bien, idéntico a &x int &*xxx = &r; //error, no tiene sentido
Una referencia a un puntero, sin embargo, es perfectamente razonable, ya que un puntero es un tipo normal y corriente:
1 2 3 4 5 | int x; int *p = &x; int *&r = p; //bien int *&xxx = &x; //error: &x no es un L-valor int *const &cpr = &x; //bien, referencia constante a R-valor |
Naturalmente, como se ve en la línea 3, una referencia a un puntero debe vincularse a una variable de tipo puntero (un L-valor) y no como en la línea 4, a la dirección de otra variable (un R-valor). A no ser, claro que la referencia sea constante, como en la línea 5.
Un ejemplo de la utilidad de las referencias a punteros es para implementar una caché con std::map:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Cache { private: typedef std::map<int, Objeto *> cache_t; cache_t m_cache; public: ~Cache() { for (cache_t::iterator it = m_cache.begin(); it != m_cache.end(); ++it) delete it->second; } Objeto *Get(int key) { Objeto *&obj = m_cache[key]; if (!obj) obj = new Objeto(key); return obj; } }; |
En la línea 14, el std::map::operator[] busca el puntero solicitado, si lo encuentra lo devuelve, y si no, lo inserta inicializado por defecto y lo devuelve. En realidad devuelve una referencia al puntero almacenado en el mapa, eso es lo que permite escribir m_cache[key] = puntero;. En la línea 15 se está asignando al puntero contenido en el mapa. De esta manera se busca y se inserta en la cache con una única operación de búsqueda.
Artículos relacionados:
- Referencias Hay quien considera que las referencias de C++ no son...
- Referencias a R-valor (2ª parte) En el post anterior describía cómo las referencias a R-valor...
- Referencias a R-valor (1ª parte) En un post anterior comentaba que en C++ no se...
- Punteros vs Arrays (parte 1 de 2) En C y, por herencia, en C++ los punteros y...
- Punteros vs Arrays (parte 2 de 2) En el post anterior describí las diferencias principales entre array...
RSS
Deja un comentario