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& var, tipoX &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í sí 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.
[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.