El lenguaje de programación C++: referencias

Continuamos con la serie de artículos dedicados a darle caña a C++. Hablamos hoy de las referencias y de su significado.

Una definición genérica e intuitiva de referencia podría ser la de una construcción del lenguaje que nos permite acceder a una estructura de datos.

En Java por ejemplo, casi todo lo que entendemos como «declaraciones de variables» son realmente referencias. Sólo son verdaderas variables aquellos identificadores cuyo tipo es primitivo (a saber: boolean, char, byte, short, int, long, float y double).

double g = 9.8; // Aquí g es una variable clásica
g.isInfinite(); // No tine sentido, g no es una estructura de datos, es una variable
Double r = new Double(9.8); // Aquí r es una referencia a un objeto Double. En Java, por convenio, las clases de objetos comienzan por una letra mayúscula
r.isInfinite(); // Esto sí tiene sentido, como referencia, r nos permite acceder al objeto

Referenciar no tiene por qué estar ligado a la programación orientada a objetos. Desreferenciar un puntero a una estructura, en C, puede verse como un acto referencial a la estructura apuntada por el puntero:

struct vector { int x; int y; } v;
struct vector *pv = &v;
(*pv); // La construcción (*pv) es una referencia a v
(*pv).x = 0;
pv->y = 1; // La flecha es una forma abreviada de desreferenciar y acceder a un campo. Más azúcar sintáctico.

Hasta aquí parece fácil. Veamos por qué la cosa se complica en C++.

Referencias en C++

Según Stroustup:

Una referencia es un nombre alternativo para un objeto*. El principal uso de las referencias es la especificación de parámetros y valores de retorno de las funciones en general y de los operadores sobrecargados (…) en particular. La notación X& significa referencia a X.

*Aquí, un objeto se refiere a cualquier estructura de datos, sea una variable, un puntero, un vector, una estructura, una clase…

En C++ una referencia se comporta como una especie de «nombre alternativo» para un objeto. De hecho, una referencia necesita ser inicializada en el momento de la declaración. Tras ello, una referencia jamás dejará de referirse al objeto de su inicialización. Considera el siguiente ejemplo:

int i = 1;
int j = 42;
int &r = i; // Esto no asigna, ENLAZA el nombre r al nombre i
r = j; // Esto NO re-enlaza r al nombre j sino que cambiará el valor de i
printf("%n", i); // Saldrá impreso 42

Puede parecer confuso pero es que hay una diferencia vital entre las líneas 3 y 4 del código anterior. En la línea 3, el signo igual no se utiliza para asignar, sino para enlazar a un objeto. A partir de este punto, el nombre r es 100% equivalente a escribir i. Con esto en mente, la línea 4 puede reinterpretarse como:

i = j;

donde el signo igual sí tiene su significado clásico de asignación.

Quizá, una mejor solución hubiese sido introducir un nuevo signo para expresar el enlace como <-> o algo así. Entonces el código anterior quedaría:

int i = 1;
int j = 42;
int &r <-> i; // Queda claro que NO es una asignación, sino un enlace
r = j; // Y está claro que esto SÍ es una asignación
printf("%n", i); // Saldrá impreso 42

Detalles oscuros de la implementación provocan la siguiente situación:

int &r = 1; // ¡ERROR! Se necesita un valor-i* para inicializar una referencia
const int &s = 1; // Esto está bien
const int &t = 1.5; // Incluso esto está bien

*Un valor-i es una expresión del lenguaje que podría ir en el lado izquierdo de una asignación.

Aunque el libro de Stroustup no lo explique así, la línea 1 es erronea porque el tipo de los literales es const tipo, es decir, que 1 es del tipo const int. Un tipo constante no puede ser un valor-i, es decir, no puede aparecer a la izquierda de un igual lo que nos impide hacer cosas extrañas como 1 = 3 (¿trata de asignar 3 a 1?). Por otro lado, la línea 2 es correcta porque los tipos coinciden y la línea 3 también lo es porque se aplican las reglas de conversión implícita.

Antes de terminar, el lector puede haberse dado cuenta de que permitir enlazar una referencia a algo que no es un nombre (ya que un literal es un valor) es algo que contradice lo que dijimos en un principio acerca de que una referencia no es más que otro nombre para un nombre ya declarado. Por eso, realmente y por debajo, al enlazar con un literal se está introduciendo una variable temporal y luego es esta la que se enlaza con la referencia. Así, el siguiente ejemplo está extraido del libro:

const double &cdr = 1; // correcto

puede compararse a:

double temp = double(1); // primero crea un temporal con el valor correcto
const double &cdr = temp; // después utiliza el temporal como iniciador de cdr

Con ello podemos ver que cdr sí está enlazado con un nombre: temp (aunque en la práctica, no sepamos qué nombre tiene el temporal).

Bueno, pues hasta aquí por el momento. Mañana continuaremos indagando en los usos más comunes de las referencias. Os recuerdo que si algo no ha quedado claro o es impreciso, ¡indicadlo en los comentarios!

2 comentarios en “El lenguaje de programación C++: referencias

Deja un comentario