Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.2.3 Referencias

Nota: aunque las hemos incluido bajo el rubro general §4.2 dedicado a los punteros, desde el punto de vista de la tipología, las referencias tienen muy poco o nada que ver con aquellos; unos y otros son tipos distintos. Sin embargo, existen similitudes en cuanto a su funcionalidad y a un cierto maridaje entre ambos tipos, en especial en el paso de parámetros a funciones.

§1 Sinopsis 

Las referencias son un tipo de dato C++ estrechamente relacionado con los punteros. Una referencia de un objeto no es un objeto [5], en el sentido que no tiene su propio espacio de almacenamiento como ocurre con los punteros, y en consecuencia no pueden realizarse con ellas muchas de las operaciones que se relacionan con objetos. Por ejemplo, obtener su dirección, crearlas con el operador new, o crear matrices de referencias.

Una referencia es una especie de alias o "alter ego" del objeto. Como se verá a continuación , este concepto, que también existe en otros lenguajes [7], es un recurso de C++ para pasar argumentos a funciones permitiendo que los argumentos no sean simples variables locales de la función, sino objetos del ámbito que realiza la invocación, lo que permite que la función pueda modificar objetos externos a ella.

Nota: en este capítulo nos referimos a las denominadas referencias tradicionales, o de Lvalue ("lvalue references") para distinguirlas de otro tipo, las referencias de Rvalue ("rvalue references") que son una adición que será incluida en la próxima revisión del Estándar C++, cuyo símbolo es && [8].

Nota: hemos leído en algún libro [2] que "las referencias se incluyeron en C++ porque al Sr. Stroustrup pensó que hacían más simple la programación (los programadores noveles podrían evitar los punteros)". En realidad, la justificación de este nuevo tipo es más profunda y sutil. El paso de "referencias" a funciones esta suficientemente resuelto en C y C++ con los punteros, y desde luego es mi opinión que el Sr. Stroustrup no estaba pensando precisamente en los programadores noveles cuando diseñó su lenguaje. Él mismo nos indica: "El uso principal de las referencias es para especificar argumentos (parámetros) y valores devueltos de funciones en general y para operadores sobrecargados en particular" [3]. Al tratar de la sobrecarga de operadores ( 4.9.18c) veremos una explicación del verdadero sentido de esta afirmación. La realidad es que las referencias fueron introducidas como un artificio imprescindible en el mecanismo de sobrecarga de operadores.

§2 Sintaxis:

La declaración de una variable de este tipo se realiza mediante el declarador de referencia &. La sintaxis general es:

<tipo_objeto> & <etiqueta_referencia> [ = <iniciador> ]

Ejemplo:

int x;

...

int & z = x;  // decimos que x es el 'iniciador' y que z es la 'referencia'

Estas sentencias declaran e inicia la variable z como referencia-a-entero, y la asocia con la variable x (que es un entero). En adelante z actúa como un alias de x, de forma que cualquier operación sobre z equivale a hacerla sobre x. En realidad puede considerarse que z es "casi" un sinónimo de x (como si fuesen la misma variable). Por ejemplo, si hacemos:

int x = 4;

int & z = x;   // z es referencia-a-int; z == 4    §2a

z = z * 4;     // z == 16, x == 16


En el párrafo anterior hemos dicho "casi" porque la referencia y el objeto referenciado son de tipo distinto. Por ejemplo, si nos referimos a las expresiones anteriores, x y z son de tipos distintos; x es tipo int, z es referencia-a-int (int&).


§2.1 Observe que las expresiones: tipoX& vartipoX &var y tipoX & var son todas equivalentes. En consecuencia, las tres expresiones que siguen también lo son [1]:

int &z = x;
int& z = x;
int & z = x;


Cualquiera de estas expresiones crea el Lvalue z como un alias para la variable x (suponiendo que el iniciador es del mismo tipo que la referencia). Cualquier operación en z tiene exactamente el mismo efecto que la operación en x. Por ejemplo: z = 2 asigna 2 a x, y &z devuelve la dirección de x. Esto significa que siguiendo la expresiónes §2a podemos expresar:

int* iptr = &z;       // Ok. puntero a x

§3 Observaciones

En muchas situaciones prácticas puede ser indiferente utilizar una referencia o un puntero, sin embargo mantienen importantes diferencias que conviene conocer y que resumimos en el cuadro adjunto antes de comentarlas más detenidamente. En cualquier caso, la mejor regla es recordar que las referencias pueden considerarse como un sustituto del identificador de un objeto, mientras que los punteros deben ser considerados siempre como objetos en sí mismos [6]. Además sus aritméticas son distintas.

Posibilidad de: Referencias Punteros
Declaración independiente de la definición No Si
Asignarles un nuevo valor No Si
Referirse a un objeto de su mismo tipo No Si
Asignarles el valor void No Si
§4 Declaración

Las referencias no pueden ser declaradas aisladas, de forma que tienen que estar indefectiblemente unidas a un objeto en su propia definición (deben ser inicializadas en la declaración). Además, una vez declaradas no pueden ser reasignadas a otro objeto (como los punteros), por lo que resultan unidas de por vida al objeto inicial [4]. Por ejemplo:

int& z;       // Error.

int& z = x    // Ok.

int& z = y;   // Error.

Por la razón anterior, puesto que tienen que estar unidas a un objeto, no pueden referenciar a void:

int& z = void;   // Error.

Sí pueden ser inicializadas a otra referencia del mismo tipo, en cuyo caso señalan al objeto inicial. Ejemplo:

int& max (int& a, int& b) {

  return (a >= b)? a : b;

}

...

int x = 10, y = 30;

int& r1 = x;         // Ok. r1 referencia a x

int& r2 = r1;        // Ok. r2 referencia a x

int& r3 = max(x, y); // Ok. r3 referencia a y

También se pueden definir referencias a matrices:

int m[5];

int (&rm)[5] = m;    // Ok. rm referencia a m

rm[1] = R3;          // Ok. m[1] == 30


Según hemos visto hasta ahora, podemos imaginar que las referencias son una especie de punteros constantes, pero que no aceptan el álgebra de punteros ni pueden ser manipuladas igual.

  Existe un caso especial en la declaración de referencias: cuando estas son miembros de clase. Acabamos de indicar que deben ser inicializadas en la declaración, pero de otro lado, la sintaxis C++ no permite asignaciones en el cuerpo de la clase. La discrepancia se ha resuelto con un mecanismo especial de inicialización que solo es válido en estos casos ( 4.11.2d3).

Referencias a objetos de clases implícitas ( 4.12.2).

§5 Reasignación de referencias:

Después de la definición inicial, las sucesivas asignaciones a las referencias deben ser con objetos del mismo tipo. Por ejemplo, a una referencia-a-int solo se le pueden asignar tipos int. Pero estas asignaciones son en realidad al objeto inicialmente referenciado. Ejemplo:

int x = 10, y = 20;

int& refi = x;             // definición inicial

cout << "X = " << refi;    // -> X = 10 (valor de x)

refi = y;                  // Ojo!! Equivale a: x = y

cout << "X = " << refi;    // -> X = 20 (x es ahora 20)

cout << "X = " << x;       // -> X = 20  (comprobación)

§6 Modelado:

En ocasiones el compilador realiza automáticamente determinadas promociones de tipo ( 4.9.9) para hacer posible la asignación. Por ejemplo, siguiendo con las definiciones anteriores (§5 ):

enum COLOR { ROJO, VERDE, AZUL};
COLOR c1 = VERDE;
refi = c1;                     // L.3: Ok!! (ahora x = 1)
cout << "X = " << refi;        // -> X = 1

float f = 12.5;
refi = f;                      // L.6: Ok!!
cout << "F = " << f;           // -> F = 12.5
cout << "X = " << refi;        // -> X = 12 (Atención!!)

En la asignación L.3, la variable enumerada c1 es promovida a entero (cosa perfectamente factible, 4.7), el resultado es asignado a refi, lo que a la postre equivale a asignarlo a x. Un caso parecido es el de la asignación L.6, donde el float f es promovido a entero (lo que implica una pérdida de precisión) y el resultado asignado nuevamente a refi, la diferencia resultante entre f y x se muestra en las dos últimas salidas.


Hay que hacer notar que la sustitución del modelado implícito por uno explícito no altera para nada el resultado de este tipo de conversiones. Aunque el nuevo Estándar recomienda realizar todos los modelados de forma explícita. Entre otras razones, para facilitar la búsqueda y depuración de posibles errores.

Versión con modelado explícito ( 4.9.9b) de las sentencias anteriores:

int x = 10, y = 20;

int& refi = x;

cout << "X = " << refi;          // -> X = 10

refi = y;                        // Ok!! (casting inecesario)

cout << "X = " << refi;          // -> X = 20

enum COLOR { ROJO, VERDE, AZUL};
COLOR c1 = VERDE;
refi = static_cast<COLOR> (c1);  // modelado estático: Ok.
cout << "X = " << refi;          // -> X = 1

float f = 12.5;
refi = static_cast<float> (f);   // modelado estático: Ok
cout << "F = " << f;             // -> F = 12.5

cout << "X = " << refi;          // -> X = 12 (Atención!!)


En ocasiones los tipos del Rvalue y Lvalue de la asignación son tan dispares que el modelado no es posible (ni implícito ni explícito), por lo que el compilador muestra un mensaje de error al intentar la asignación:

struct C {float x; float y; } c1;

refi = c1;                    // Error!!

refi = static_cast<C> (c1);   // Error !!

en ninguno de estos dos casos es posible el modelado, ya que en ninguna circunstancia una estructura puede ser promovida a un entero, por lo que el compilador avisa con un error: Cannot convert 'C' to 'int' in function...

§7 Punteros y referencias a referencias

No es posible declarar punteros-a-referencias ni referencias-a-referencias:

int x = 10;

int& rti = x;       // Ok referencia-a-x

int&* ptrti = &rti  // Error!! Puntero-a-referencia

int&& rar = rti     // Error!! Referencia-a-referencia


Puesto que las referencias-a-tipoX son en realidad un "alter ego" del tipo referenciado, sí es posible utilizar referencias para la definición de otras referencias al mismo tipo:

int& rti2 = rti;    // Ok otra referencia-a-x

En contra de lo que ocurre con sus parientes cercanos los punteros, no es posible iniciar referencias con el operador new.

§8 Referencias a punteros

Aunque de poca importancia práctica, la referencia-a-puntero, es un alias que puede ser utilizado a todos los efectos como si fuese el propio puntero. Ejemplo:

#include <iostream.h>


int main() {               // ======
  int x = 10;
  int* ptr = &x;           // puntero-a-int
  int*& ref = ptr;         // referencia-a-puntero-a-int
  cout << "ptr-a-X = " << ref << endl;
  cout << "ptr-a-X = " << ptr << endl;
  cout << "Valor X = " << *ref << endl;  // M.6
  cout << "Valor X = " << *ptr << endl;
}

Salida:

ptr-a-X = 0065FE00
ptr-a-X = 0065FE00
Valor X = 10
Valor X = 10

En este ejemplo es digna de mención la forma de declaración de ref, referencia-a-puntero; y como a todos los efectos, incluso para aplicarle el operador de indirección * ( 4.9.11), la referencia se comporta en M.6 como un alias perfecto del puntero, señalando al mismo objeto que aquel.

§9 Referencias a funciones

C++ permite definir referencias a funciones, aunque carecen de importancia práctica. La sintaxis para su declaración es la misma que con los punteros, aunque como es usual, su inicialización debe hacerse en el punto de la declaración. Así mismo, pueden invocarse funciones a través de sus referencias como si se tratara de punteros.

Ejemplo:

float sum(int i, int j) {
  float s = i + j;
  cout << "La suma es: " << s << endl;
  return s;
}
...
int main() {         // ===============

  float (*fptr)(int, int) = sum;   // puntero-a-función (iniciado)
  float (&fref)(int, int) = sum;   // referencia-a-función
  int x = 2, y = 5;

  sum(x=2, y);                     // invocación estándar
  fptr(x*2, y);                    // invocación mediante puntero

  fref(x*2, y);                    // Ok. invocación mediante referencia

  int& rx = x;                     // nuevo alias de x

  int& ry = y;                     // nuevo alias de y
  fref(rx*2, ry);                  // Ok. invocación mediante referencia

}

Observe que los argumentos pasan "por valor" en todas las invocaciones (incluyendo la última).


§10  Si en la declaración de una referencia, el iniciador es una constante o un objeto de tipo diferente que el referenciado, entonces se crea un objeto temporal para el que la referencia actúa como un alias. Considere los siguientes ejemplos:

int& z = 6;

Se crea un objeto temporal tipo int que recibe el valor 6; se crea también una referencia-a-int de nemónico z, a ese objeto temporal. El compilador avisa de esta circunstancia con una advertencia: Temporary used to initialize 'z' in function.... Observe que el objeto temporal solo es accesible a través de su referencia (no tiene nemonico ni puntero específicos).

float f = 12.1;

int& z = f;

Aquí se crea un objeto temporal tipo float de nemónico f y Rvalue 12,1. En la segunda sentencia se crea un objeto temporal tipo int que recibe el valor de f (que es promovido a int antes de la asignación). Se crea una referencia-a-int que señala a dicho objeto temporal. El compilador avisa de esta circunstancia con el mismo mensaje que en el caso anterior: Temporary used to initialize 'z' in function.... Como consecuencia final, z contiene una imagen de f que es como su "versión int".

Completemos el ejemplo anterior con una asignación:

float f = 12.1;

int& z = f;

z = 10;

El proceso es análogo al anterior. En la tercera sentencia se asigna un valor 10 al objeto temporal, mientras que f sigue conservando su valor inicial. Como comprobación, escribimos lo anterior en forma de programa ejecutable:

#include <iostream.h>


int main() {
  float f = 12.1;
  cout << "Valor f: " << f << endl;
  int& z = f;
  cout << "Valor z: " << z << endl;
  z = 10;
  cout << "Valor f: " << f << endl;
  cout << "Valor z: " << z << endl;
}

Salida:

Valor f: 12.1
Valor z: 12
Valor f: 12.1
Valor z: 10


§11 Consideremos también una variación del ejemplo relativo al valor devuelto por una función ( 4.4.7), modificando el tipo aceptado en el segundo parámetro de la función max y añadiendo un "casting" para que en cualquier caso, el valor devuelto sea adecuado a la declaración.

#include <iostream.h>


int& max (int& a, long& b) {if (a >= b) return a; return int(b); }

int main () {          // ===========
  int x =12, y = 22;
  cout << "Máximo: " << max(x, y) << endl;   // M.2:

  cout << "Valor inicial: " << y << endl;

  int& mx = max(x, y);              // M.4: Ok asignación del mismo tipo

  mx = 30;                          // M.5:

  cout << "Valor final: " << y << endl;

}

Salida:

Máximo: 22
Valor inicial: 22
Valor final: 22

En cada invocación a la función max (M.2 y M.4), el compilador nos señala idéntico mensaje de aviso: Temporary used for parameter 'b' in call to 'max(int &,long &)' in function main(). A su vez, la salida nos muestra que el valor y permanece inalterado. La razón, como se ha señalado antes, es que en cada invocación en el ámbito de main, se crea un objeto temporal de tipo referencia-a-long, que recibe el valor 22L, que es pasado a la función como segundo argumento (b). Este objeto temporal es promovido a int antes de su devolución por el return. En consecuencia, se devuelve una referencia a un objeto temporal, objeto que es modificado después en M.5. Como queda de manifiesto en las salidas, la variable y permanece inalterada durante todo el proceso .

§12 Argumentos por referencia:

Aunque hemos insistido en que la razón última de la introducción de referencias en C++ es posibilitar la sobrecarga de operadores, en la práctica su uso más frecuente es el paso de argumentos a funciones "por referencia"; en especial cuando se trata de objetos definidos por el usuario (instancias de clases). Observe que mientras en C clásico solo se pasan argumentos por valor, en C++ es posible pasar argumentos por valor y por referencia.

Ejemplo:

void func1 (int);   // declara argumento de tipo int
void func2 (int&);  // declara argumento de tipo referencia-a-int
 ...

int sum = 3;
func1(sum);         // sum pasa por valor
func2(sum);         // sum pasa por referencia


Observe la utilización del argumento es idéntica en la invocación de ambas funciones, como si fuese "por valor". La diferencia en uno y otro caso estriba solo en la forma de declarar los argumentos en la definición de la función.

El argumento sum pasado por referencia en func2, puede ser modificado directamente desde dentro de esta función. Por contra, en func1 el argumento pasa por valor; la función recibe una copia de la variable sum, por lo que no puede modificar el valor original (la variable sum existente fuera de la función func1). Ver al respecto: Argumentos por valor/por referencia ( 4.4.5).

Tenga en cuenta que en ocasiones, especialmente cuando los argumentos son objetos (instancias de clases), el verdadero motivo de pasar objetos "por referencia" no es precisamente para que la función pueda modificar el argumento (incluso se intenta evitar esto declarando el argumento como referencia constante), sino por razones de eficacia del código .

En realidad, más que alguna nueva "funcionalidad", el paso de argumentos a funciones "por referencia" solo proporciona cierta comodidad adicional a la funcionalidad proporcionada por los punteros. Intentaremos aclararlo con un ejemplo muy sencillo:


#include <iostream>   // distintas formas de paso de argumentos
using namespace std;

int func(int v, int& r, int* p) {      // L4:
  cout << "Valores recibidos:" << endl;
  cout << "S4 v == " << v << endl;
  cout << "S5 r == " << r << endl;
  cout << "S6 p == " << p << endl;
  ++v; ++r; ++*p;                      // L9:
  cout << "Valores modificados:" << endl;
  cout << "S7 v == " << v << endl;
  cout << "S8 r == " << r << endl;
  cout << "S9 p == " << p << endl;
  return v;
}

int main(void) {       // =========
  int x = 10; int y = 12; int z = 14;   // M1:
  int* pz = &z;
  cout << "Valores iniciales:" << endl; // M3:
  cout << "S1 x == " << x << endl;
  cout << "S2 y == " << y << endl;
  cout << "S3 z == " << z << endl;

  int r = func(x, y, pz);               // M8:

  cout << "Valores finales:" << endl;   // M10:
  cout << "Sa x == " << x << endl;
  cout << "Sb y == " << y << endl;
  cout << "Sc z == " << z << endl;
  return 0;
}

Salida:

Valores iniciales:
S1 x == 10
S2 y == 12
S3 z == 14
Valores recibidos:
S4 v == 10
S5 r == 12
S6 p == 0065FDFC
Valores modificados:
S7 v == 11
S8 r == 13
S9 p == 0065FDFC
Valores finales:
Sa x == 10
Sb y == 13
Sc z == 15

Comentario:

En L4 definimos una función que recibe tres argumentos: un entero (int); una referencia-a-int (int&), y un puntero-a-int (int*). Esta función se limita a mostrar los valores recibidos. A continuación altera estos valores (locales a la función), y finalmente muestra los valores modificados.

Observe que el argumento p es un puntero. Por consiguiente, una alteración del tipo ++p en L9, modificaría su valor en el sentido de que señalaría a una posición distinta de la actual (la variable z de main). Esto podría tener resultados impredecibles, incluyendo un error fatal de runtime si la nueva dirección estuviese fuera del área asignada al programa por el SO. Por esta razón se ha utilizado la forma *++p para modificarla. En realidad esta instrucción incrementa en una unidad el objeto señalado (la variable z de main).

El programa comienza en main, donde definimos tres enteros y un puntero-a-int; estos valores serán utilizados más tarde como argumentos en la invocación de la función.

A continuación el programa muestra los valores iniciales de estos objetos. Son las sentencias M3/M5 que proporcionan el primer grupo de salidas con los resultados esperados. 

En M8, se invoca la función con los valores definidos en M1 y M2. Aquí resultan pertinentes algunas observaciones:

El primer parámetro (v) esperado por func es un entero. En consecuencia, se utiliza la variable x "tal cual" como primer argumento de la invocación.

Como segundo parámetro (r) la función espera una referencia-a-entero. Sin embargo no ha sido necesario construir previamente un objeto de este tipo. Como en el caso anterior, se pasa un entero (y) "tal cual" como segundo argumento. Aquí es el compilador el que se encarga de construir un objeto local en la función, de tipo referencia-a-entero, e iniciarlo con el valor de y. Es decir, para este argumento el compilador interpreta:

int& r = y;

A partir de ahora el objeto r (local a func) es un alias del objeto y de la función invocante (main). Es aquí donde está el truco y la magia del paso de argumentos "por referencia". El truco tiene en realidad dos partes:

  • Primero: no es necesario copiar el objeto, solo se pasa una etiqueta (podríamos decir que un puntero muy especial). En el ejemplo se ha pasado un int, pero cuando se pasan objetos por referencia suelen ser grandes, generalmente tipos abstractos (estructuras y clases) cuya copia puede resultar costosa en espacio de pila y en tiempo de carga en el marco correspondiente ( 4.4.6b), de modo que el paso por referencia supone un incremento notable en la eficiencia del programa (volveremos sobre esto a continuación ).

  • Segundo: el resultado es como si el ámbito del objeto representado se "alargara" al interior de la función (podríamos incluso utiliza el mismo nombre que en el exterior), lo que resulta en una gran comodidad sintáctica.

    En L9 hemos incrementado y en una unidad través de su "alias" r. Lo se comprueba en las salidas S8 y Sb. Observe que un incremento análogo en el primer parámetro v pasado "por valor", no tiene absolutamente ninguna influencia sobre el argumento x utilizado por la función invocante (salidas S7 y Sa).


El tercer parámetro (p) esperado por la función es un puntero. Observe que aquí ha sido necesario construir un objeto del tipo adecuado (pz) para utilizarlo como argumento en la invocación. Un intento análogo a los anteriores, utilizar el entero z "tal cual" como tercer argumento, habría originado la protesta del compilador: Error: Cannot convert 'int' to 'int *' in function main()....

Observe que en la invocación

int r = func(x, y, pz);    // M8:

el tercer argumento contiene el valor de la dirección de la variable z. El mismo resultado podría obtenerse utilizando:

int r = func(x, y, &z);    // M8b:

Nótese que aquí el símbolo & no es un declarador de referencia como en L4, sino el operador de referencia ( 4.9.11b) que obtiene la dirección de z, y es este valor el que se pasa como argumento (ver observación al final ).

En la salida Sc comprobamos cómo desde dentro de la función, también ha sido posible modificar el valor de un objeto externo (variable z), utilizando un puntero que ha pasado "por valor". Sin embargo, la sintaxis involucrada es algo más complicada, dado que es necesario deferenciar el puntero.

§13  Valores devueltos por referencia:

Otro uso común de este tipo de objetos es su utilización como valor de retorno de una función. Es el caso del valor devuelto por la función adjunta

int& max (int& a, int& b) { if (a >= b) return a; return b; }

Más información al respecto en: Valor devuelto ( 4.4.7).

§14  Criterios de eficiencia

En ocasiones la utilización de referencias para el paso de argumentos a funciones o en el valor devuelto, está motivada solo por criterios de eficacia. En efecto, las secuencias de llamada y retorno de funciones ( 4.4.6b) implican la creación de todas las variables locales de la función (incluyendo el valor que será devuelto), así como la invocación del constructor-copia para todos los argumentos que no han sido pasados por referencia. Cuando estos objetos son muy grandes. Por ejemplo, en instancias de clases con muchos datos o que derivan de jerarquías complejas, se requieren procesos de creación y destrucción, a veces muy complicados, que pueden evitarse parcialmente utilizando referencias. En tales casos suele recurrirse a declarar constantes los argumentos pasados "por referencia" para evitar que la función pueda alterar su valor.

Ver ejemplos al respecto: 4.9.18a; 4.9.18b2


  Además de ser utilizado como declarador de referencia, el símbolo & (ámpersand) puede ser utilizado como operador AND entre bits ( 4.9.3) y como operador de referencia ( 4.9.11b). Nótese la diferencia entre las dos expresiones que siguen:

int x;

...

int* p = &x; // declara p puntero-a-entero, ahora contiene la dirección de x

int& p = x;  // declara p referencia-a-entero ahora es sinónimo de x


En la primera línea se ha usado & como operador de referencia; en la segunda como declarador de referencia.

  Inicio.


[1] Particularmente prefiero la forma  tipoX& var. La razón es la misma que expuse para los punteros. tipoX&, por si mismo representa referencia-a-tipoX.

[2]  Michael C. Daconta "C++ Pointers and Dynamic Memory Management" A Wiley/QED publication 1995. §4.2  

[3]  Stroustrup TC++PL  §5.5 References.

[4]  En C++ no existe por tanto la posibilidad existente en otros lenguajes (php por ejemplo) de desasignar ("unset") una referencia una vez creada.

[5]  "References are not first class citizens in C++". Stroustrup & Ellis: ACRM §12.3.2

[6]  Observe que tanto los identificadores como los punteros y las referencias se utilizan para "referenciar" entidades (generalmente objetos) del programa, y es lícito pensar que cuando se utilizan un identificador o una referencia, el compilador realiza automáticamente una deferenciación, de modo que se manipula el objeto referenciado. Sin embargo se precisa una deferenciación explícita para operar sobre el objeto referenciado cuando se utiliza un puntero.

[7] Tenga en cuenta no obstante, que la nomenclatura utilizada no es consistente entre los distintos lenguajes. Por ejemplo, las "referencias" de Perl son más parecidas a los punteros C++ que a las comentadas aquí.

[8]  Howard E. Hinnant, Bjarne Stroustrup y Bronek Kozicki: "A Brief Introduction to Rvalue References"    artima.com/cppsource/rvalue.html.