La Increíble Referencia De Const Que No Es Const
Mientras trabajaba en la biblioteca NamedType, me encontré con una situación que me dejó aturdido: una referencia const que permite modificar el objeto al que se refiere. Sin const_cast
. Sin un mutable
. Sin nada en la manga.
¿Cómo puede ser esto? ¿Y cómo hacer cumplir el const
en esa referencia const?
Una referencia const que no es const
Aquí hay una descripción de una situación en la que aparece esta referencia extraña. Considere la siguiente clase de envoltura:
template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};
Esta clase Wrapper
almacena un valor de tipo T
, y le da acceso a través del método get()
(independientemente de eso, si se pregunta si “get” es un buen nombre, es posible que desee leer esta discusión detallada). La parte importante es que el método get()
solo da acceso de solo lectura al valor almacenado, porque devuelve una referencia const.
Para comprobarlo, instanciemos la plantilla con int
, por ejemplo. Intentar modificar el valor almacenado a través del método get()
no se compila (intente hacer clic en el botón Ejecutar a continuación).
Lo cual está bien, porque queremos que get()
sea de solo lectura.
Pero ahora instanciemos Wrapper
con int&
. Un caso de uso para esto podría ser evitar una copia o realizar un seguimiento de posibles modificaciones de a
, por ejemplo. Ahora intentemos modificar el valor almacenado a través de la referencia const devuelta por get()
:
Y si usted golpea la EJECUCIÓN buttong verás que… compila! Y imprime el valor modificado a través de la referencia const, como si nada especial hubiera pasado.
¿No es desconcertante? ¿No siente la impresión tranquilizadora de seguridad que proporciona const
colapsando a nuestro alrededor? ¿En quién podemos confiar?
Intentemos entender qué está pasando con esta referencia.
¿Referencia a Const o referencia a const?
El quid de la cuestión es que nuestra referencia a es una referencia a const, pero no una referencia a const.
Para entender lo que eso significa, vamos a descomponer paso a paso lo que está sucediendo en la creación de instancias de plantilla.
El método get()
devuelve un const T&
, con T
procedente de template T
. En nuestro segundo caso, T
es int&
, por lo que const T&
es const (int&) &
.
Echemos un vistazo más de cerca a esto const (int&)
. int&
es una referencia que se refiere a un int
, y se le permite modificar ese int
. Pero const (int&)
es una referencia int&
que es const
, lo que significa que la referencia en sí no se puede modificar.
¿Qué significa, para empezar, modificar una referencia? En teoría, significaría hacer que se refiera a otro objeto, o que no se refiera a nada. Pero ambas posibilidades son ilegales en C++: una referencia no puede volver a enlazar. Se refiere al mismo objeto durante todo el curso de su vida.
Así que ser const
no dice mucho para una referencia, ya que siempre son const
, ya que no pueden volver a enlazar. Esto implica que const (int&)
es efectivamente el mismo tipo que int&
.
Lo que nos deja con get()
regresando (int&) &
. Y según las reglas de colapso de referencias, esto es int&
.
Así que get
devuelve un int&
. Su interfaz dice T const&
, pero en realidad es int&
. No es un código muy expresivo, ¿verdad?
Cómo hacer de la referencia const una referencia a const
Ahora que hemos pasado el primer paso de entender el problema, ¿cómo podemos solucionarlo? Es decir, ¿cómo podemos hacer que get()
devuelva una referencia al valor almacenado que no permite modificarlo?
Una forma de hacer esto es insertar explícitamente un const
dentro de la referencia. Podemos hacer esto eliminando la referencia, agregando un const
y volviendo a colocar la referencia. Para quitar la referencia, usamos std::remove_reference
en C++11, o el más conveniente std::remove_reference_t
en C++14:
Como puede ver si hace clic en Ejecutar, el error de compilación esperado ahora aparece al intentar modificar el valor almacenado en Wrapper
.
Creo que es útil entender lo que está pasando con esta referencia, porque este es un código que parece seguro, pero en realidad no lo es, y este es el tipo más peligroso.
Puede jugar con el código de los parques infantiles anteriores incrustados en la página. Y un gran agradecimiento al usuario de desbordamiento de pila rakete1111 por ayudarme a entender las razones detrás de este problema.
También te puede gustar
- El Análisis Más Molesto: Cómo Detectarlo y Arreglarlo rápidamente