c++ - static_cast - static cast
C++: ¿Por qué es const_cast malvado? (9)
Sigo escuchando esta declaración, mientras que realmente no puedo encontrar la razón por la cual const_cast es malo.
En el siguiente ejemplo:
template <typename T>
void OscillatorToFieldTransformer<T>::setOscillator(const SysOscillatorBase<T> &src)
{
oscillatorSrc = const_cast<SysOscillatorBase<T>*>(&src);
}
Estoy usando una referencia, y al usar const, estoy protegiendo mi referencia de ser modificada. Por otro lado, si no uso const_cast, el código no se compilará. ¿Por qué const_cast sería malo aquí?
Lo mismo se aplica al siguiente ejemplo:
template <typename T>
void SysSystemBase<T>::addOscillator(const SysOscillatorBase<T> &src)
{
bool alreadyThere = 0;
for(unsigned long i = 0; i < oscillators.size(); i++)
{
if(&src == oscillators[i])
{
alreadyThere = 1;
break;
}
}
if(!alreadyThere)
{
oscillators.push_back(const_cast<SysOscillatorBase<T>*>(&src));
}
}
Por favor, dame algunos ejemplos, en los que puedo ver que es una mala idea / no profesional usar un const_cast.
Gracias por cualquier esfuerzo :)
usando const, estoy protegiendo mi referencia de ser cambiada
Las referencias no se pueden cambiar; una vez inicializadas, siempre se refieren al mismo objeto. Una referencia que es const
significa que el objeto al que se refiere no se puede cambiar. Pero const_cast
deshace esa afirmación y permite que el objeto sea cambiado después de todo.
Por otro lado, si no uso const_cast, el código no se compilará.
Esto no es una justificación para nada. C ++ se niega a compilar el código que puede permitir que se cambie un objeto const
porque ese es el significado de const
. Tal programa sería incorrecto. const_cast
es un medio para compilar programas incorrectos: ese es el problema.
Por ejemplo, en tu programa, parece que tienes un objeto
std::vector< SysOscillatorBase<T> * > oscillators
Considera esto:
Oscillator o; // Create this object and obtain ownership
addOscillator( o ); // cannot modify o because argument is const
// ... other code ...
oscillators.back()->setFrequency( 3000 ); // woops, changed someone else''s osc.
Pasar un objeto por referencia constante no solo significa que la función llamada no puede cambiarlo, sino que la función no puede pasarlo a otra persona que pueda cambiarlo. const_cast
viola eso.
La fortaleza de C ++ es que proporciona herramientas para garantizar cosas sobre la propiedad y la semántica de valores. Cuando deshabilita esas herramientas para compilar el programa, habilita errores. Ningún buen programador lo considera aceptable.
Como solución a este problema en particular, es probable que el vector
(o el contenedor que esté utilizando) guarde los objetos por valor, no por puntero. Entonces addOscillator
puede aceptar una referencia constante y, sin embargo, los objetos almacenados son modificables. Además, el contenedor es el propietario de los objetos y garantiza que se eliminen de forma segura, sin ningún trabajo de su parte.
Básicamente, const le promete a usted y al compilador que no cambiará el valor. La única vez que debe usar cuando usa una función de biblioteca C (donde const no existe), se sabe que no cambia el valor.
bool compareThatShouldntChangeValue(const int* a, const int* b){
int * c = const_cast<int*>(a);
*c = 7;
return a == b;
}
int main(){
if(compareThatShouldntChangeValue(1, 7)){
doSomething();
}
}
El const_cast
sería malo porque le permite romper el contrato especificado por el método, es decir, "No modificaré src
". La persona que llama espera que el método se apegue a eso.
El uso de const_cast
por cualquier motivo que no sea la adaptación a bibliotecas ( antiguas ) donde las interfaces tienen punteros o referencias no constantes, pero las implementaciones no modifican los argumentos, es incorrecto y peligroso .
La razón por la cual está mal es porque cuando su interfaz toma una referencia o un puntero a un objeto constante, está prometiendo no cambiar el objeto. Otro código puede depender de que no modifiques el objeto. Considere por ejemplo, un tipo que contiene un miembro caro de copiar, y que junto con eso contiene algunas otras invariantes.
Considere un vector<double>
y un valor promedio precalculado, el * promedio se actualiza cada vez que se agrega un nuevo elemento a través de la interfaz de clase, ya que es barato actualizarlo en ese momento, y si se solicita con frecuencia no hay necesidad de recalcularlo desde el datos cada vez. Debido a que el vector es caro de copiar, pero podría ser necesario el acceso de lectura, el tipo podría ofrecer un acceso barato que devuelva un std::vector<double> const &
para el código de usuario para verificar los valores que ya están en el contenedor. Ahora, si el código de usuario descarta la constidad de la referencia y actualiza el vector, la invariante de que la clase retiene el promedio se rompe y el comportamiento de su programa se vuelve incorrecto.
También es peligroso porque no tiene garantía de que el objeto que le pasen sea realmente modificable o no. Considere una función simple que toma una cadena terminada nula C y la convierte en mayúscula, bastante simple:
void upper_case( char * p ) {
while (*p) {
*p = ::to_upper(*p);
++p;
}
}
Ahora supongamos que decide cambiar la interfaz para tomar una const char*
y la implementación para eliminar la const
. El código de usuario que funcionaba con la versión anterior también funcionará con la nueva versión, pero algunos códigos que se marcarían como un error en la versión anterior no se detectarán en el momento de la compilación. Considere que alguien decidió hacer algo tan estúpido como upper_case( typeid(int).name() )
. Ahora el problema es que el resultado de typeid
no es solo una referencia constante a un objeto modificable , sino más bien una referencia a un objeto constante . El compilador puede almacenar type_info
objeto type_info
en un segmento de solo lectura y el cargador para cargarlo en una página de solo lectura de la memoria. Intentar cambiarlo bloqueará su programa.
Tenga en cuenta que, en ambos casos, no puede saber, a partir del contexto de const_cast
si se mantienen invariantes adicionales (caso 1) o incluso si el objeto es realmente constante (caso 2).
En el extremo opuesto, la razón de que const_cast
exista fue la adaptación al viejo código C que no const_cast
la palabra clave const
. Durante algún tiempo, funciones como strlen
tomarían un char*
, aunque se sabe y documenta que la función no modificará el objeto. En ese caso, es seguro usar const_cast
para adaptar el tipo, no para cambiar la const-ness . Tenga en cuenta que C tiene soporte para const
desde hace mucho tiempo, y const_cast
tiene usos adecuados menores.
Es al menos problemático. Tienes que distinguir dos constnesses:
constness de la variable instanciada
Esto puede provocar constness físico, los datos se colocan en un segmento de solo lecturaconstness del parámetro de referencia / puntero
Esta es una constness lógica, solo aplicada por el compilador
Puedes expulsar la const solo si no está físicamente const, y no puedes determinar eso desde el parámetro.
Además, es un "olor" que algunas partes de tu código sean const-correct, y otras no. Esto a veces es inevitable.
En su primer ejemplo, asigna una referencia constante a lo que supongo que es un puntero no const. Esto le permitiría modificar el objeto original, que requiere al menos un molde constante. Para ilustrar:
SysOscillatorBase<int> a;
const SysOscillatorBase<int> b;
obj.setOscillator(a); // ok, a --> const a --> casting away the const
obj.setOscilaltor(b); // NOT OK: casting away the const-ness of a const instance
Lo mismo se aplica a su segundo ejemplo.
Probablemente necesites definir tu contenedor como contenedor de objetos const
template <typename T> struct Foo {
typedef std::vector<SysOscillator<T> const *> ossilator_vector;
}
Foo::ossilator_vector<int> oscillators;
// This will compile
SysOscillator<int> const * x = new SysOscillator<int>();
oscillators.push_back(x);
// This will not
SysOscillator<int> * x = new SysOscillator<int>();
oscillators.push_back(x);
Dicho esto, si no tienes control sobre el typedef para el contenedor, quizás esté bien const_cast en la interfaz entre tu código y la biblioteca.
Usted está violando un contrato de codificación. Marcar un valor como const es decir que puedes usar este valor pero nunca cambiarlo. const_cast rompe esta promesa y puede crear un comportamiento inesperado.
En los ejemplos que das, parece que tu código no es del todo correcto. oscillatorSrc probablemente debería ser un puntero constante, aunque si realmente necesitas cambiar el valor, entonces no debes pasarlo como una constante.
Porque estás frustrando el propósito de const
, que es evitar que modifiques el argumento . Por lo tanto, si descarta la const
de algo, no tiene sentido e hincha el código, y le permite romper las promesas que le hizo al usuario de la función de que no modificará el argumento.
Además, usar const_cast
puede causar un comportamiento indefinido. Considera este código:
SysOscillatorBase<int> src;
const SysOscillatorBase<int> src2;
...
aFieldTransformer.setOscillator(src);
aFieldTransformer.setOscillator(src2);
En la primera llamada, todo está bien. Puedes descartar la consistencia de un objeto que no es realmente const
y modificarlo bien. Sin embargo, en la segunda llamada, en setOscillator
está setOscillator
consistencia de un verdadero objeto const
. Si alguna vez modificas ese objeto allí en cualquier lugar, estás causando un comportamiento indefinido al modificar un objeto que realmente es const
. Como no puedes decir si un objeto marcado const
es realmente const
donde fue declarado, nunca const_cast
usar const_cast
menos que estés seguro de que nunca jamás podrás mutar el objeto. Y si no lo haces, ¿cuál es el punto?
En su código de ejemplo, está almacenando un puntero no const
a un objeto que podría ser const
, lo que indica que tiene la intención de mutar el objeto (de lo contrario, ¿por qué no simplemente almacenar un puntero en const
?). Eso podría causar un comportamiento indefinido.
Además , hacerlo de esa manera permite que las personas pasen temporalmente a su función:
blah.setOscillator(SysOscillatorBase<int>()); // compiles
Y luego está almacenando un puntero a un temporal que no será válido cuando la función devuelva 1 . No tiene este problema si toma una referencia no const
.
Por otro lado, si no uso const_cast, el código no se compilará.
Luego cambie su código, no agregue un molde para que funcione. El compilador no está compilando por una razón. Ahora que conoce las razones, puede hacer que sus punteros de vector
hold sean consistentes en lugar de formar un agujero cuadrado en uno redondo para que se ajuste a su clavija.
Entonces, en general, sería mejor que tu método aceptara una referencia no const
, y el uso de const_cast
casi nunca es una buena idea.
1 En realidad, cuando la expresión en la que se llamó a la función termina.
, mientras que realmente no puedo encontrar la razón por la cual const_cast es malo.
No lo es, cuando se usa responsablemente y cuando sabes lo que estás haciendo . (¿O realmente copió y pegó el código de todos los métodos que difieren solo por su modificador const?)
Sin embargo, el problema con const_cast es que puede desencadenar un comportamiento indefinido si lo usa en una variable que originalmente era const . Es decir, si declara la variable const, entonces const_cast e intenta modificarla. Y un comportamiento indefinido no es algo bueno.
Su ejemplo contiene precisamente esta situación: posiblemente la variable const convertida en no const. Para evitar el problema, almacene const SysOscillatorBase<T>*
(const pointer) o SysOscillatorBase<T>
(copy) en su lista de objetos, o pase de referencia en lugar de const reference.