¿Por qué es casi imposible construir contenedor de referencias (en C ++)?

La dificultad es que una referencia en C ++ no puede estar vacía.

A todos los defensores de los lenguajes de GC como Java les gusta criticar a C ++ diciendo “¿Punteros? ¡Eww! “, La realidad es que las referencias de Java son en su mayoría como punteros C ++, no referencias C ++ (* gasp *). La única ventaja real de facilidad de uso en las referencias de Java sobre los punteros de C ++ es

  1. La sintaxis del descriptor de acceso: el hecho de que una referencia de Java sea un puntero queda oculto por el hecho de que el operador del descriptor de acceso “.” Desreferencia internamente cuando el operando es una referencia, ocultando efectivamente su “puntero-ness”
  2. La función del recolector de basura para asegurarse de no tener que limpiar su memoria de almacenamiento dinámico. Pero esto no tiene nada que ver con que sea una referencia en lugar de un tipo de puntero. El hecho de que la variable que contiene un objeto en Java es un tipo complejo que facilita la recolección y reflexión de basura es una peculiaridad de Java en comparación con C ++, donde dicha variable es solo la memoria real, y si desea esos servicios, debe preguntar para ellos (use punteros inteligentes).

Las referencias de Java son anulables. Por lo tanto, deben verificarse como nulos y, por lo tanto, su código puede tener una excepción nula si accede sin verificar primero. ¡Eso es simplemente un puntero con un recolector de basura y una sintaxis azucarada!

En realidad, es bastante fácil crear un contenedor (no especificó de qué tipo) si lo diseña con la idea de que una referencia no puede ser nula. Esto significa que no se pueden borrar, pero también significa que no se pueden crear antes de tiempo, que es donde el almacenamiento en matriz se vuelve problemático. Si desea un contenedor de listas de referencias, puede escribir una seleccionando usar una lista vinculada. Ahora cada referencia se asigna según sea necesario y se puede inicializar correctamente. Por supuesto, tendrá que usar punteros internamente para implementar la lista enlazada.

Ahora es posible que se esté refiriendo a la implementación de un contenedor de referencia que sigue los patrones STL. Esto es problemático, tanto por el problema de nulos discutido, como porque no se puede sobrecargar el operador “.” En C ++. Los contenedores STL siguen un patrón que permite a los iteradores que pueden ser desreferenciados acceder al objeto mismo. Esto se hace sobrecargando “*” y “->”, pero para una referencia se sobrecargaría “.” En su lugar. Eso no está permitido porque, como afirma Bjarne Stroustroup, no pudo encontrar la manera de crear un “.” Que no permitiera interminables casos de recursión (la función de operador de punto implica un operador de punto adicional, que llamaría el mismo función, etc.) Sin embargo, Bjarne ahora ha declarado que tiene una solución para esto y una adición que permite que esto sea posible para C ++.

Pensar en

int v1 = 1, v2 = 2;
int & r1 = v1, & r2 = v2;
int * p1 = & v1, * p2 = & v2;

// que hace esto?
p1 = p2; // r1 = r2; //

Como ve, la semántica de referencia implica que las asignaciones de referencia deben realizarse en los objetos referidos (mientras que el puntero es … designado).

Ahora, piense en un std::vector : ¿qué sucede cuando inserta o quita un elemento en el medio? Todos los elementos que pasaron el punto de inserción deben ser “desplazados” en un lugar. Esto requiere una re-referenciación de referencia (como el reenfoque del puntero), pero como la desreferenciación de referencia está implícita, esto no se puede hacer.

Como la familia de contenedores también contiene contenedores empaquetados (como vector o matriz o cadena, donde todos los elementos deben ser consecutivos), esto requiere que los elementos sean al menos movibles (si es que no son asignables) y las referencias no lo son.

Como muchos otros señalaron, hay un sustituto de referencia que es std::reference_wrapper (que se puede construir a través de las funciones std::ref o std::cref helper, ver std :: reference_wrapper – cppreference.com)

Esto no es realmente una referencia, sino una clase que contiene un … ejem … puntero , con una conversión de referencia implícita y un operador de asignación de trabajo en referencia. Esto permite que estas clases participen en la llamada de función tomando los objetos que refieren como parámetro, pero no son un reemplazo exacto para la referencia: al menos hasta C ++ 14 incluido, operator. no es imposible, de modo que para acceder a un miembro referido a reference_wrapper, tiene que convertir o llamar a una función explícita de miembro get() . Esto no permite escribir el mismo código genérico para valores y valores envueltos de referencia.

Hay una propuesta estándar para el operator. superior operator. (ver http://www.open-std.org/jtc1/sc2 …), para tener “referencia inteligente” (dual con punteros inteligentes, que son verdaderos equivalentes de puntero) que pueden, de hecho, comportarse como referencias semánticas.

Para agregar una respuesta formal, no hay Contenedores estándar (std :: vector, etc.) de referencias porque todos requieren que su tipo de elemento sea Borrable, lo que a su vez requiere que se pueda formar un puntero para el tipo de elemento. No hay punteros a las referencias porque las referencias no son objetos y no tienen dirección. Si intenta crear una instancia de std :: vector , el mensaje de error dirá exactamente eso:

gcc:

usr / local / include / c ++ / 6.2.0 / bits / alloc_traits.h: 372: 27: error: formando un puntero al tipo de referencia ‘int &’

sonido metálico:

usr / local / llvm-head / include / c ++ / v1 / memory: 1750: 16: error: ‘puntero’ declarado como un puntero a una referencia de tipo ‘int &’

msvc:

: \ Archivos de programa (x86) \ Microsoft Visual Studio 14.0 \ VC \ INCLUDE \ xmemory0 (590): error C2528: ‘const_pointer’: el puntero a referencia es ilegal

Hay, como se menciona en todas las demás respuestas, contenedores especializados de referencias (que contienen una referencia cada uno) llamados std :: reference_wrapper, que pueden servir como tipos de elementos en std :: vector, etc. En muchos sentidos, un puntero simple (y, por extensión, puntero inteligente) también se puede ver como un contenedor que contiene una referencia.

En realidad, no es tan difícil: prueba con std::reference_wrapper

Como escribió el usuario Quora. El objeto instanciado std :: reference_wrapper permite que el código manipule una referencia. La implementación probablemente usa un puntero internamente – vea std :: reference_wrapper – cppreference.com.

En general, debería considerar el uso de contenedores estándares de C ++ con punteros inteligentes.