Espacios de nombres
Variantes
Acciones

Declaración de referencias

De cppreference.com
< cpp‎ | language
 
 
 
 

Declara una variable con nombre como una referencia. Es decir, un alias a un objeto que ya existe o a una función.

Contenido

[editar] Sintaxis

Una declaración de variable de referencia es cualquier declaración simple cuyo declarador tiene la forma

& atrib(opcional) declarador (1)
&& atrib(opcional) declarador (2) (desde C++11)
1) Declarador de referencia lvalue: la declaración S& D; declara a D como una referencia lvalue al tipo determinado por sec-decl-especificadores S.
2) Declarador de referencia rvalue: la declaración S&& D; declara a D como una referencia rvalue al tipo determinado por sec-decl-especificadores S.
declarador - cualquier declarador excepto otro declarador de referencia (no existen las referencias a referencias)
atrib(C++11) - lista opcional de atributos

Se requiere que una referencia se inicialice para que se refiera a un objeto o función válidos. Véase inicialización de referencias.

No existen las referencias a void ni las referencias a referencias.

Los tipos de referencia no pueden estar calificado-cv en el nivel más alto; no existe la sintaxis para eso en la declaración, y si una calificación se introduce mediante typedef, decltype o un argumento de tipo de plantilla, se ignora..

Las referencias no son objetos; no necesariamente ocupan almacenamiento, aunque el compilador puede asignar almacenamiento si es necesario implementar la semántica deseada (p. ej., un dato miembro no-estático de tipo de referencia habitualmente aumenta el tamaño de la clase con la cantidad necesaria para almacenar una dirección de memoria).

Ya que las referencias no son objetos, no existen arrays de referencias, ni punteros a referencias, ni referencias a referencias:

int& a[3]; // error
int&* p;   // error
int& &r;   // error
Colapso de referencias

Se permite formar referencias a referencias mediante manipulaciones de tipos en plantillas of typedefs, y en tal caso se aplican las reglas de colapso de referencias: una referencia rvalue a una referencia rvalue se colapsa en una referencia rvalue, mientras que todas las otras combinaciones forman referencias lvalue:

typedef int&  lref;
typedef int&& rref;
int n;
lref&  r1 = n; // tipo de r1 es int&
lref&& r2 = n; // tipo de r2 es int&
rref&  r3 = n; // tipo de r3 es int&
rref&& r4 = 1; // tipo de r4 es int&&

(Esto, junto con las reglas especiales para la deducción de argumentos de plantilla cuando T&& se usa en una plantilla de función, forma las reglas que hacen posible a std::forward.)

(desde C++11)

[editar] Referencias lvalue

Las referencias lvalue pueden ser utilizadas para crear un alias a un objeto existente (opcionalmente con una calificación-cv diferente):

#include <iostream>
#include <string>
 
int main() {
    std::string s = "Ej";
    std::string& r1 = s;
    const std::string& r2 = s;
 
    r1 += "emplo";           // modifica a s
//  r2 += "!";               // error: no puede modificar a través de referencia a const
    std::cout << r2 << '\n'; // imprime s, que ahora mantiene "Ejemplo"
}


También pueden ser usadas para implementar semántica de paso-por-referencia en llamadas de función:

#include <iostream>
#include <string>
 
void double_string(std::string& s) {
    s += s; // 's' es el mismo objeto que 'str' de main()
}
 
int main() {
    std::string str = "Prueba";
    double_string(str);
    std::cout << str << '\n';
}


Cuando el valor de retorno de una función es una referencia lvalue, la expresión de llamada de función se vuelve una expresión lvalue:

#include <iostream>
#include <string>
 
char& char_number(std::string& s, std::size_t n) {
    return s.at(n); // string::at() devuelve una referencia a char
}
 
int main() {
    std::string str = "Test";
    char_number(str, 1) = 'a'; // la llamada de función es lvalue, se le puede asignar
    std::cout << str << '\n';
}


Referencias rvalue

Las referencias rvalue references pueden ser utilizadas para extender las duraciones de objetos temporales (observa que las referencias lvalue a const (p. ej. const T& también pueden extender las duraciones de objetos temporales, pero tales objetos no pueden modificarse a través de ellas):

#include <iostream>
#include <string>
 
int main() {
    std::string s1 = "Prueba";
//  std::string&& r1 = s1;           // ERROR: no puede vincularse a lvalue
 
    const std::string& r2 = s1 + s1; // de acuerdo: referencia lvalue reference a const extiende la duración
//  r2 += "Prueba";                    // ERROR: no se puede modificar a través de referencia a  const
 
    std::string&& r3 = s1 + s1;      // de acuerdo: referencia rvalue reference extiende duración
    r3 += "Prueba";                    // de acuerdo: puede modificarse a través de referencia a no-const
    std::cout << r3 << '\n';
}


De manera más importante, cuando una función tiene sobrecargas tanto de referencia rvalue como referencia lvalue, la sobrecarga que tiene la referencia rvalue se vincula a rvalues (incluyendo tanto prvalues como xvalues)), mientras que la sobrecarga que contiene la referencia lvalue se vincula a lvalues:

#include <iostream>
#include <utility>
 
void f(int& x) {
    std::cout << "sobrecarga de referencia lvalue f(" << x << ")\n";
}
 
void f(const int& x) {
    std::cout << "sobrecarga de referencia lvalue a const f(" << x << ")\n";
}
 
void f(int&& x) {
    std::cout << "sobrecarga de referencia rvalue f(" << x << ")\n";
}
 
int main() {
    int i = 1;
    const int ci = 2;
    f(i);  // llama a f(int&)
    f(ci); // llama a f(const int&)
    f(3);  // llama a f(int&&)
           // llamaría a f(const int&) si la sobrecarga f(int&&) no se hubiera suministrado
    f(std::move(i)); // llama a f(int&&)
 
    // las variables de referencia rvalue son lvalues cuando se usan en expresiones
    int&& x = 1;
    f(x);            // llama a f(int& x)
    f(std::move(x)); // llama a f(int&& x)
}


Esto permite los constructores de movimiento, los operadores de asignación de movimiento, así como otras funciones conscientes de movimiento (p. ej., std::vector::push_back()) para que se seleccionen automáticamente cuando sea adecuado.

Como las referencias rvalue pueden vincularse a xvalues, pueden referirse a objetos no-temporales:

int i2 = 42;
int&& rri = std::move(i2); // se vincula directamente a i2

Esto hace posible mover el contenido de un objeto en ámbito que ya no se necesita:

std::vector<int> v{1,2,3,4,5};
std::vector<int> v2(std::move(v)); // se vincula a una referencia rvalue a v
// assert(v.empty()); // la aserción podría ejecutarse; véase nota a continuación
(desde C++11)

Nota: Un objeto del cual se han reutilizado sus recursos se encuentra en un estado estable pero su contenido es indefinido.

Referencias reenviantes

Las referencias reenviantes son un tipo especial de referencias que preservan la categoría de valor de un argumento de función, haciendo posible reenviarlo por medio de std::forward. Las referencias reenviantes son bien:

1) un parámetro de función de una plantilla de función declarado como una referencia rvalue no-calificada-cv parámetro de tipo de plantilla de esa misma plantilla de función:
template<class T>
int f(T&& x) {                    // x es una referencia reenviante
    return g(std::forward<T>(x)); // y también puede ser reenviada
}
 
int main() {
    int i;
    f(i); // el argumento es lvalue, llama a f<int&>(int&), std::forward<int&>(x) es lvalue
    f(0); // el argumento es rvalue, llama a f<int>(int&&), std::forward<int>(x) es rvalue
}
 
template<class T>
int g(const T&& x); // x no es una referencia reenviante: const T no está no-calificado-cv
 
template<class T> struct A {
    template<class U>
    A(T&& x, U&& y, int* p); // x no es una referencia reenviante: T no es un
                             // parámetro de tipo de plantilla del constructor,
                             // pero y es una referencia reenviante
};
2) auto&& excepto cuando se deduce de una lista inicializadora rodeada por llaves:
auto&& vec = foo();       // foo() puede ser un lvalue o un rvalue, vec es una referencia reenviante
auto i = std::begin(vec); // funciona de cualquier manera
(*i)++;                   // funciona de cualquier manera
g(std::forward<decltype(vec)>(vec)); // reenvía, preservando la categoría de valor
 
for (auto&& x: f()) {
  // x es una referencia reenviante; esta es la manera más segura de usar un bucle for basado en rango
}
 
auto&& z = {1, 2, 3}; // *no* es una referencia reenviante (caso especial para listas inicializadoras)

Véase también deducción de argumentos de plantilla y std::forward.

(desde C++11)

[editar] Referencias pendientes

Aunque las referencias, una vez que han sido inicializadas, siempre se refieren a objetos válidos o funciones, es posible crear un programa donde la duración del objeto referido termina, pero la referencia permanece accesible (pendiente). Acceder a tal referencia resulta en comportamiento indefinido. Un ejemplo común es una función que devuelve una referencia a una variable automática:

std::string& f()
{
    std::string s = "Ejemplo";
    return s; // sale del ámbito de s:
              // se llama a su destructor y su almacenamiento se desasigna
}
 
std::string& r = f(); // referencia pendiente
std::cout << r;       // comportamiento indefinido: lee de una referencia pendiente
std::string s = f();  // comportamiento indefinido: inicialización por copia de una referencia pendiente

Observa que las referencias rvalue references y las referencias lvalue a const extienden las duraciones de los objetos temporales (véase inicialización de referencias para las reglas y excepciones).

Si el objeto referido fue destruído (p. ej., por una llamada explícita al destructor), pero su almacenamiento no fue desasignado, puede usarse una referencia al objeto cuya duración terminó, de maneras limitadas, y puede volverse válida si el objeto es recreado en el mismo almacenamiento (véase acceso fuera de la duración para detalles).