Articulo de referencia

Referencia (C++)

En el lenguaje de programación C++ , una referencia es un tipo de dato de referencia simple , menos potente pero más seguro que el tipo de puntero heredado de C. El nombre " ref...

En el lenguaje de programación C++ , una referencia es un tipo de dato de referencia simple , menos potente pero más seguro que el tipo de puntero heredado de C. El nombre " referencia C++" puede generar confusión, ya que en informática una referencia es un tipo de dato de concepto general, mientras que los punteros y las referencias C++ son implementaciones específicas de tipos de datos de referencia. La definición de una referencia en C++ es tal que no necesita existir. Puede implementarse como un nuevo nombre para un objeto existente (similar a la palabra clave `rename` en Ada).

Sintaxis y terminología

Referencia del valor L

La declaración del formulario:

T y r ;

donde Tes un tipo y res un identificador define un identificador cuyo tipo es una referencia lvalue aT . [ 1 ]

Ejemplos:

int a = 5 ; int & refA = a ;extern int & refB ;

Aquí, refAy refBson de tipo "referencia lvalue a int"

entero & foo ();

fooes una función que devuelve una "referencia lvalue a int"

barra vacía ( int & p );

bares una función con un parámetro de referencia p, que es una "referencia lvalue a int"

clase MyClass { int & b ; };

MyClasses un classcon un miembro que es una referencia lvalue aint

int fn () { return 42 ; }int ( & fnRef )() = fn ; int ( && fnRef2 )() = fn ; // esencialmente equivalente a lo anterior

fnes una función que devuelve un (tipo no referencia) inty fnRefes un alias parafn

const int & ref = 65 ;

const int& refes una referencia lvalue que const intapunta a una pieza de almacenamiento con valor 65.

int a [ 3 ]; int ( & alvr )[ 3 ] = arr ; int ( && arvr )[ 3 ] = std :: move ( a ); using Triplet = int [ 3 ]; int ( && aprvl )[ 3 ] = Triplet {}; // Triplet{} es un valor pr de matriz int * const & pclv = a ; // igual que int* const& pclv = &a[0]; int *&& prv = a ; // int*& alv = a; // Error: Inicializando una referencia lvalue a un tipo no constante con un valor r

alvres una referencia a un array. Al inicializar una referencia a un array, no se produce la conversión de array a puntero, pero sí se produce al inicializar una referencia a un puntero. Dado que la conversión de array a puntero devuelve un prvalue, solo las referencias lvalue consty rvalue pueden inicializarse con su resultado. De manera similar, al inicializar una referencia a una función, no se produce la conversión de función a puntero (véase fnRefmás arriba), pero sí se produce al inicializar una referencia a un puntero a función :

int fn () { return 42 ; }int ( * const & pFnRef )() = fn ; // lo mismo que int (*const &pFnRef)() = &fn; int ( *&& pFnRef2 )() = fn ;

Referencia del valor R

La declaración del formulario:

T && r ;

donde Tes un tipo y res un identificador define un identificador cuyo tipo es una referencia rvalue aT . Dado que el nombre de una referencia rvalue es en sí mismo un lvalue, std::movedebe usarse para pasar una referencia rvalue a una sobrecarga de función que acepte un parámetro de referencia rvalue. Las referencias rvalue a parámetros de plantilla de tipo no calificados cv de esa misma plantilla de función o auto&&excepto cuando se deducen de una lista de inicializador encerrada entre llaves se llaman referencias de reenvío (denominadas "referencias universales" en algunas fuentes antiguas [ 2 ] ) y pueden actuar como referencias lvalue o rvalue dependiendo de lo que se les pase. [ 3 ] Cuando se encuentran en parámetros de función, a veces se usan con std::forward()para reenviar el argumento de la función a otra función mientras se conserva la categoría de valor (lvalue o rvalue) que tenía cuando se pasó a la función que llama. [ 4 ]

Los tipos que son de tipo "referencia a T" a veces se denominan tipos de referencia . Los identificadores que son de tipo referencia se denominan variables de referencia . Sin embargo, llamarlos variable es, de hecho, un término inapropiado , como veremos.

Significado y limitaciones

Las referencias no son objetos y solo pueden referirse a objetos o funciones. No se permiten arreglos de referencias, punteros a referencias ni referencias a referencias porque requieren tipos de objetos. , y causarán errores de compilación (mientras que (referencia de arreglo) y (referencia de puntero) no lo harán si están inicializadas). Las referencias a también están mal formadas porque no es un tipo de objeto o función , pero sí pueden existir referencias a .int&i[4]int&*iint&&iint(&i)[4]int*&ivoidvoidvoid*

Declarar referencias como consto volatile( ) también falla a menos que se use una / en cuyo caso la / se ignora. Sin embargo, si se lleva a cabo la deducción de argumentos de plantilla y se deduce un tipo de referencia (lo que sucede cuando se usan referencias de reenvío y se pasa un lvalue a la función) o si , o denotan un tipo de referencia, es posible tomar una referencia a ese tipo. En ese caso, la regla que se usa para determinar el tipo de referencia se llama colapso de referencia y funciona así: Suponiendo un tipo y un tipo de referencia a , intentar crear una referencia rvalue a crea un mientras que una referencia lvalue a crea una referencia lvalue a . En otras palabras, las referencias lvalue anulan las referencias rvalue y las referencias rvalue de referencias rvalue permanecen sin cambios.volatileint&itypedefdecltypeconstvolatiletypedefusingdecltypeTTTRTRTRTRT

usando LrefInt = int & ; usando RrefInt = int && ;int i = 0 ;LrefInt & r1 = i ; // r1 es de tipo int& const LrefInt & r2 = i ; // r2 es de tipo int& const LrefInt && r3 = i ; // r3 es de tipo int&RrefInt & r4 = i ; // r4 es de tipo int& RrefInt && r5 = 5 ; // r5 es de tipo int&&decltype ( r2 ) & r6 = i ; // r6 tiene el tipo int& decltype ( r2 ) && r7 = i ; // r7 tiene el tipo int&

Una función miembro no estática puede declararse con un calificador ref. Este calificador participa en la resolución de sobrecarga y se aplica al parámetro de objeto implícito como consty , volatilepero a diferencia de estos dos, no cambia las propiedades de this. Lo que hace es exigir que la función se llame en una instancia lvalue o rvalue de la clase.

importar std ;struct A { A () = default ;void display () const & { std :: println ( "lvalue" ); }void display () const && { std :: println ( "rvalue" ); } };int main () { A a ; a.display (); // imprime "lvalue" std :: move ( a ) .display ( ) ; // imprime "rvalue" A (). display (); // imprime "rvalue" A && b = std :: move ( a ); b.display (); // imprime "lvalue"(! ) }

Relación con los punteros

Las referencias en C++ difieren de los punteros en varios aspectos esenciales:

  • Una referencia en sí misma no es un objeto, sino un alias; cualquier aparición de su nombre se refiere directamente al objeto al que hace referencia. Una declaración de puntero crea un objeto puntero que es distinto del objeto al que apunta.
    • Los contenedores de referencias no están permitidos porque las referencias no son objetos, mientras que los contenedores de punteros son habituales en el polimorfismo.
    • No está permitido crear una referencia de referencia, ya que las referencias solo pueden referirse a objetos (o funciones).
  • Las referencias no pueden desinicializarse. Dado que es imposible reinicializar una referencia, deben inicializarse tan pronto como se crean.
  • Una vez creada una referencia, no se puede volver a usar para referenciar otro objeto; no se puede reconfigurar . Esto se suele hacer con punteros.
  • Las referencias no pueden ser nulas , mientras que los punteros sí; toda referencia apunta a algún objeto, aunque este puede ser válido o no.

En particular, las variables locales y globales deben inicializarse donde se definen, y las referencias que son miembros de datos de las instancias de clase deben inicializarse en la lista de inicialización del constructor de la clase. Por ejemplo:

  • int & k ; // El compilador mostrará el siguiente error: `k' se declaró como referencia pero no se inicializó.

Existe una conversión sencilla entre punteros y referencias: el operador de dirección ( &) genera un puntero que apunta al mismo objeto cuando se aplica a una referencia, y una referencia inicializada a partir de la desreferenciación ( *) de un valor de puntero apuntará al mismo objeto que dicho puntero, siempre que esto sea posible sin provocar un comportamiento indefinido . Esta equivalencia refleja la implementación típica, que compila las referencias en punteros que se desreferencian implícitamente en cada uso. Si bien esto suele ser así, el estándar de C++ no obliga a los compiladores a implementar las referencias mediante punteros.

Una consecuencia de esto es que, en muchas implementaciones, operar con una variable con tiempo de vida automático o estático a través de una referencia, aunque sintácticamente similar a acceder a ella directamente, puede implicar operaciones de desreferenciación ocultas que resultan costosas.

Además, debido a que las operaciones con referencias son tan limitadas, son mucho más fáciles de entender que los punteros y son más resistentes a errores. Si bien los punteros pueden invalidarse mediante diversos mecanismos, que van desde transportar un valor nulo hasta operaciones aritméticas fuera de límites, conversiones de tipo ilegales o generarlos a partir de enteros arbitrarios, una referencia previamente válida solo se invalida en dos casos:

  • Si se refiere a un objeto con asignación automática que queda fuera del ámbito,
  • Si se refiere a un objeto dentro de un bloque de memoria dinámica que ha sido liberado.

El primer problema se detecta fácilmente de forma automática si la referencia tiene un ámbito estático, pero sigue siendo problemático si la referencia es miembro de un objeto asignado dinámicamente; el segundo es más difícil de detectar. Estas son las únicas preocupaciones relacionadas con las referencias, y se abordan adecuadamente mediante una política de asignación razonable.

Usos de las referencias

El uso más conveniente de las referencias es para los parámetros de las funciones. Las referencias permiten leer y cambiar los valores de los argumentos de la función (parámetros reales) sin tener que usar el operador de desreferencia *cada vez que se usa el parámetro. Por ejemplo:

void cuadrado ( int x , int & out ) { out = x * x ; }

Entonces, la siguiente llamada colocaría 9 en y:

entero y ; cuadrado ( 3 , y );

Sin embargo, la siguiente llamada daría un error de compilaciónconst , ya que los parámetros de referencia lvalue que no están calificados con solo pueden vincularse a valores direccionables:

cuadrado ( 3 , 6 );
  • Devolver una referencia a lvalue permite que las llamadas a funciones se asignen a:
int & preInc ( int & x ) { return ++ x ; // "return x++;" habría sido incorrecto }preInc ( y ) = 5 ; // igual que ++y, y = 5
  • En muchas implementaciones, los mecanismos normales de paso de parámetros suelen implicar una costosa operación de copia para parámetros grandes. Las referencias calificadas con constson una forma útil de pasar objetos grandes entre funciones que evita esta sobrecarga:
    void slow ( BigObject x ) { // ... }void fast ( const BigObject & x ) { // ... }BigObject y ;lento ( y ); // Lento, copia y al parámetro x. rápido ( y ); // Rápido, da acceso directo de solo lectura a y.

Si fFastrealmente necesita su propia copia de x que pueda modificar, debe crear una copia explícitamente. Si bien se podría aplicar la misma técnica usando punteros, esto implicaría modificar cada llamada a la función para agregar operadores de dirección ( &) engorrosos al argumento, y sería igualmente difícil de deshacer si el objeto se redujera posteriormente.

Comportamiento polimórfico

Siguiendo con la relación entre referencias y punteros (en el contexto de C++), las primeras exhiben capacidades polimórficas, como cabría esperar:

importar std ;clase A { público : A () = predeterminado ;virtual void display () { std :: println ( "Esta es la clase A" ); } };clase B : público A { público : B () = predeterminado ;virtual void display () { std :: println ( "Esta es la clase B" ); } };int main () { A a ; A & refA = a ;B b ; A & refB = b ;refA.display ( ) ; refB.display ( ) ; }

El código fuente anterior es C++ válido y genera la siguiente salida:

Esta es la clase AEsta es la clase B

Referencias

  1. ISO/IEC 14822 , cláusula 9.3.3.2, párrafo 1.
  2. ^ Sutter, hierba; Stroustrup, Bjarne; Dos Reyes, Gabriel. "Reenvío de referencias" (PDF) .
  3. ISO/IEC 14822 , cláusula 13.10.2.1, párrafo 3.
  4. Becker, Thomas. "Referencias Rvalue de C++ explicadas" . Consultado el 25 de noviembre de 2022 .
  • Referencias en las Preguntas Frecuentes Lite de C++
  • Explicación de las referencias de reenvío
  • Comité Técnico Conjunto ISO/IEC JTC 1, Subcomité SC 22, Grupo de Trabajo WG 21. Norma Internacional ISO/IEC 14822 (PDF) (Borrador del Comité).{{cite book}}: CS1 maint: nombres múltiples: lista de autores ( enlace ) CS1 maint: nombres numéricos: lista de autores ( enlace )