Namespaces
Variants

Reference declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Declara una variable nombrada como una referencia, es decir, un alias para un objeto o función ya existente.

Contenidos

Sintaxis

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

& attr  (opcional) declarator (1)
&& attr  (opcional) declarator (2) (desde C++11)
1) Declarador de referencia Lvalue : la declaración S & D ; declara D como una referencia lvalue al tipo determinado por decl-specifier-seq S .
2) Declarador de referencia a valor derecho : la declaración S && D ; declara D como una rvalue reference al tipo determinado por decl-specifier-seq S .
declarator - cualquier declarator excepto declarador de referencia, declarador de array , y declarador de puntero (no existen referencias a referencias, arrays de referencias, o punteros a referencias)
attr - (since C++11) lista de attributes

Se requiere que una referencia sea inicializada para referirse a un objeto o función válido: consulte reference initialization .

El tipo "referencia a (posiblemente calificado con cv) void " no puede formarse.

Los tipos de referencia no pueden ser cv-qualified en el nivel superior; no existe sintaxis para eso en la declaración, y si se añade una calificación a un typedef-name o decltype specifier, (since C++11) o type template parameter , se ignora.

Las referencias no son objetos; no necesariamente ocupan almacenamiento, aunque el compilador puede asignar almacenamiento si es necesario para implementar la semántica deseada (e.g. un miembro de datos no estático de tipo referencia usualmente incrementa el tamaño de la clase por la cantidad necesaria para almacenar una dirección de memoria).

Debido a que las referencias no son objetos, no existen arreglos de referencias, no hay punteros a referencias, y no hay 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 o typedefs, en cuyo caso se aplican las reglas de colapso de referencias : la referencia a valor sobre referencia a valor colapsa en referencia a valor, todas las demás combinaciones forman referencia a valor izquierdo:

typedef int&  lref;
typedef int&& rref;
int n;
lref&  r1 = n; // type of r1 is int&
lref&& r2 = n; // type of r2 is int&
rref&  r3 = n; // type of r3 is int&
rref&& r4 = 1; // type of r4 is int&&

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

(desde C++11)

Referencias Lvalue

Las referencias Lvalue pueden utilizarse para crear un alias de un objeto existente (opcionalmente con diferente calificación cv):

#include <iostream>
#include <string>
int main()
{
    std::string s = "Ex";
    std::string& r1 = s;
    const std::string& r2 = s;
    r1 += "ample";           // modifica s
//  r2 += "!";               // error: no se puede modificar mediante referencia a const
    std::cout << r2 << '\n'; // imprime s, que ahora contiene "Example"
}

También pueden utilizarse para implementar semántica de paso por referencia en llamadas a funciones:

#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 = "Test";
    double_string(str);
    std::cout << str << '\n';
}

Cuando el tipo de retorno de una función es una referencia lvalue, la expresión de llamada a función se convierte en una lvalue expresión:

#include <iostream>
#include <string>
char& char_number(std::string& s, std::size_t n)
{
    return s.at(n); // string::at() returns a reference to char
}
int main()
{
    std::string str = "Test";
    char_number(str, 1) = 'a'; // the function call is lvalue, can be assigned to
    std::cout << str << '\n';
}

Referencias de valor-R

Las referencias a valor derecho pueden utilizarse para extender los tiempos de vida de objetos temporales (nótese que las referencias a lvalue a const también pueden extender los tiempos de vida de objetos temporales, pero no se pueden modificar a través de ellas):

#include <iostream>
#include <string>
int main()
{
    std::string s1 = "Test";
//  std::string&& r1 = s1;           // error: can't bind to lvalue
    const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
//  r2 += "Test";                    // error: can't modify through reference to const
    std::string&& r3 = s1 + s1;      // okay: rvalue reference extends lifetime
    r3 += "Test";                    // okay: can modify through reference to non-const
    std::cout << r3 << '\n';
}

Más importante aún, cuando una función tiene tanto referencias a rvalue como referencias a lvalue overloads , la overload de referencia a rvalue se vincula a rvalues (incluyendo tanto prvalues como xvalues), mientras que la overload de referencia a lvalue se vincula a lvalues:

#include <iostream>
#include <utility>
void f(int& x)
{
    std::cout << "lvalue reference overload f(" << x << ")\n";
}
void f(const int& x)
{
    std::cout << "lvalue reference to const overload f(" << x << ")\n";
}
void f(int&& x)
{
    std::cout << "rvalue reference overload 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 no se proporcionara la sobrecarga f(int&&)
    f(std::move(i)); // llama a f(int&&)
    // las variables de referencia a 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 que los move constructors , move assignment operators, y otras funciones compatibles con movimiento (como std::vector::push_back() ) sean seleccionadas automáticamente cuando sea apropiado.

Debido a que las referencias a valores-r pueden enlazarse a xvalues, pueden referirse a objetos no temporales:

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

Esto permite mover un objeto dentro del alcance que ya no se necesita:

std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int> v2(std::move(v)); // enlaza una referencia de valor-r a v
assert(v.empty());

Referencias de reenvío

Las referencias de reenvío son un tipo especial de referencias que preservan la categoría de valor de un argumento de función, haciendo posible reenviarlo mediante std::forward . Las referencias de reenvío son:

1) parámetro de función de una plantilla de función declarado como referencia de valor-r a parámetro de plantilla de tipo type template parameter sin calificación cv de esa misma plantilla de función:
template<class T>
int f(T&& x)                      // x es una referencia de reenvío
{
    return g(std::forward<T>(x)); // y por lo tanto 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 de reenvío: const T no está sin calificación cv
template<class T>
struct A
{
    template<class U>
    A(T&& x, U&& y, int* p); // x no es una referencia de reenvío: T no es un
                             // parámetro de plantilla de tipo del constructor,
                             // pero y es una referencia de reenvío
};
2) auto && excepto cuando se deduce de una lista de inicialización entre llaves o, cuando representa un parámetro de plantilla de una plantilla de clase durante la deducción de argumentos de plantilla de clase (desde C++17) :
auto&& vec = foo();       // foo() puede ser lvalue o rvalue, vec es una referencia de reenvío
auto i = std::begin(vec); // funciona en ambos casos
(*i)++;                   // funciona en ambos casos
g(std::forward<decltype(vec)>(vec)); // reenvía, preservando la categoría de valor
for (auto&& x: f())
{
    // x es una referencia de reenvío; esta es una forma común de usar range for en código genérico
}
auto&& z = {1, 2, 3}; // *no* es una referencia de reenvío (caso especial para listas de inicialización)

Véase también template argument deduction y std::forward .

(desde C++11)

Referencias colgantes

Aunque las referencias siempre se refieren a objetos o funciones válidos tras la inicialización, es posible crear un programa donde el lifetime del objeto referenciado finalice, pero la referencia permanezca accesible ( dangling ).

Dada una expresión expr de tipo referencia y sea target el objeto o función denotado por la referencia:

  • Si un puntero a target sería válido en el contexto de la evaluación de expr , el resultado designa target .
  • De lo contrario, el comportamiento es indefinido.
std::string& f()
{
    std::string s = "Example";
    return s; // sale del ámbito de s:
              // se llama a su destructor y se desasigna su almacenamiento
}
std::string& r = f(); // referencia colgante
std::cout << r;       // comportamiento indefinido: lectura desde una referencia colgante
std::string s = f();  // comportamiento indefinido: inicialización por copia desde una referencia colgante

Tenga en cuenta que las referencias a valor derecho y las referencias a valor izquierdo a const extienden los tiempos de vida de los objetos temporales (consulte Inicialización de referencias para las reglas y excepciones).

Si el objeto referenciado fue destruido (por ejemplo, mediante una llamada explícita al destructor), pero el almacenamiento no fue desasignado, una referencia al objeto fuera de su tiempo de vida puede usarse de maneras limitadas, y puede volverse válida si el objeto es recreado en el mismo almacenamiento (consulte Access outside of lifetime para más detalles).

Referencias inaccesibles por tipo

Intentar enlazar una referencia a un objeto donde el inicializador convertido es un lvalue (until C++11) un glvalue (since C++11) a través del cual el objeto no es type-accessible resulta en comportamiento indefinido:

char x alignas(int);
int& ir = *reinterpret_cast<int*>(&x); // comportamiento indefinido:
                                       // el inicializador hace referencia a un objeto char

Referencias incompatibles de llamada

Intentar enlazar una referencia a una función donde el inicializador convertido es un lvalue (until C++11) un glvalue (since C++11) cuyo tipo no es call-compatible con el tipo de la definición de la función resulta en comportamiento indefinido:

void f(int);
using F = void(float);
F& ir = *reinterpret_cast<F*>(&f); // comportamiento indefinido:
                                   // el inicializador hace referencia a la función void(int)

Notas

Macro de prueba de características Valor Std Característica
__cpp_rvalue_references 200610L (C++11) Referencias a valores-R

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 453 C++98 no estaba claro a qué objeto o función no se podía vincular una referencia aclarado
CWG 1510 C++11 no se podían formar referencias calificadas cv en el operando de decltype permitido
CWG 2550 C++98 los parámetros podían tener tipo "referencia a void " no permitido
CWG 2933 C++98 el comportamiento al acceder a referencias colgantes no estaba claro aclarado

Enlaces externos

Thomas Becker, 2013 - C++ Rvalue References Explained