11.2. Referencias en C++

Una referencia (&) es como un puntero constante que se de-referencia automáticamente. Normalmente se utiliza en la lista de argumentos y en el valor de retorno de de las funciones. Pero también se puede hacer una referencia que apunte a algo que no ha sido asignado. Por ejemplo:

//: C11:FreeStandingReferences.cpp
#include <iostream>
using namespace std;

// Ordinary free-standing reference:
int y;
int& r = y;
// When a reference is created, it must 
// be initialized to a live object. 
// However, you can also say:
const int& q = 12;  // (1)
// References are tied to someone else's storage:
int x = 0;          // (2)
int& a = x;         // (3)
int main() {
  cout << "x = " << x << ", a = " << a << endl;
  a++;
  cout << "x = " << x << ", a = " << a << endl;
} ///:~

Listado 11.1. C11/FreeStandingReferences.cpp


En la linea (1) el compilador asigna la cantidad necesaria de memoria, la inicializa con el valor 12, y liga la referencia a esa porción de memoria. Lo importante es que una referencia debe estar ligada a la memoria de alguien. Cuando se accede a una referencia, se está accediendo a esa memoria. Así pues, si escribe las lineas (2) y (3) incrementará x cuando se incremente a, tal como se muestra en el main(). Lo más fácil es pensar que una referencia es como un puntero de lujo. La ventaja de este «puntero» es que nunca hay que preguntarse si ha sido inicializado (el compilador lo impone) o si hay que destruirlo (el compilador lo hace).

Hay que seguir unas determinadas reglas cuando se utilizan referencias:

  1. La referencia de ser inicializada cuando se crea. (Los punteros pueden inicializarse en cualquier momento).

  2. Una vez que se inicializa una referencia, ligándola a un objeto, no se puede ligar a otro objeto. (Los punteros pueden apuntar a otro objeto en cualquier momento).

  3. No se pueden tener referencias con valor nulo. Siempre ha de suponer que una referencia está conectada a una trozo de memoria ya asignada.

11.2.1. Referencias en las funciones

El lugar más común en el que verá referencias es en los argumentos y valor de retorno de las funciones. Cuando se utiliza una referencia como un argumento de una función, cualquier cambio realizado en la referencia dentro de la función se realizará realmente sobre el argumento fuera de la función. Por supuesto que podría hacer lo mismo pasando un puntero como argumento, pero una referencia es sintácticamente más clara. (Si lo desea, puede pensar que una referencia es, nada más y nada menos, más conveniente sintácticamente).

Si una función retorna una referencia, ha de tener el mismo cuidado que si la función retornara un puntero. La referencia que se devuelva debe estar ligada a algo que no sea liberado cuando la función retorne. Si no, la referencia se referirá a un trozo de memoria sobre el que ya no tiene control.

He aquí un ejemplo:

//: C11:Reference.cpp
// Simple C++ references

int* f(int* x) {
  (*x)++;
  return x; // Safe, x is outside this scope
}

int& g(int& x) {
  x++; // Same effect as in f()
  return x; // Safe, outside this scope
}

int& h() {
  int q;
//!  return q;  // Error
  static int x;
  return x; // Safe, x lives outside this scope
}

int main() {
  int a = 0;
  f(&a); // Ugly (but explicit)
  g(a);  // Clean (but hidden)
} ///:~

Listado 11.2. C11/Reference.cpp


La llamada a f() no tiene la ventaja ni la claridad que la utilización de referencias, pero está claro que se está pasando una dirección mediante un puntero. En la llamada a g(), también se pasa una dirección (mediante una referencia), pero no se ve.

Referencias constantes

El argumento referencia en Reference.cpp funciona solamente en caso de que el argumento no sea un objeto constante. Si fuera un objeto constante, la función g() no aceptaría el argumento, lo cual es positivo porque la función modificaría el argumento que está fuera de la función. Si sabe que la función respetará las constancia un objeto, el hecho de que el argumento sea una referencia constante permitirá que la función se pueda utilizar en cualquier situación. Esto significa que para tipos predefinidos, la función no modificará el argumento, y para tipos definidos por el usuario, la función llamará solamente a métodos constantes, y no modificara ningún atributo público.

La utilización de referencias constantes en argumentos de funciones es especialmente importante porque una función puede recibir un objeto temporal. Éste podría haber sido creado como valor de retorno de otra función o explícitamente por el usuario de la función. Los objetos temporales son siempre constantes. Por eso, si no utiliza una referencia constante, el compilador se quejará. Como ejemplo muy simple:

//: C11:ConstReferenceArguments.cpp
// Passing references as const

void f(int&) {}
void g(const int&) {}

int main() {
//!  f(1); // Error
  g(1);
} ///:~

Listado 11.3. C11/ConstReferenceArguments.cpp


La llamada f(1) provoca un error en tiempo de compilación porque el compilador debe crear primero una referencia. Lo hace asignando memoria para un int, iniciánlizándolo a uno y generando la dirección de memoria para ligarla a la referencia. La memoria debe ser constante porque no tendría sentido cambiarlo: no puede cambiarse de nuevo. Puede hacer la misma suposición para todos los objetos temporales: son inaccesibles. Es importante que el compilador le diga cuándo está intentando cambiar algo de este estilo porque podría perder información.

Referencias a puntero

En C, si desea modificar el contenido del puntero en sí en vez de modificar a lo que apunta, la declaración de la función sería:

void f(int**);

y tendría que tomar la dirección del puntero cuando se llamara a la función:

int i = 47;
int* ip = &i;
f(&ip);

La sintaxis es más clara con las referencias en C++. El argumento de la función pasa a ser de una referencia a un puntero, y así no ha de manejar la dirección del puntero.

//: C11:ReferenceToPointer.cpp
#include <iostream>
using namespace std;

void increment(int*& i) { i++; }

int main() {
  int* i = 0;
  cout << "i = " << i << endl;
  increment(i);
  cout << "i = " << i << endl;
} ///:~

Listado 11.4. C11/ReferenceToPointer.cpp


Al ejecutar este programa se observa que el puntero se incrementa en vez de incrementar a lo que apunta.