Lanzamiento regular vs. static_cast vs. dynamic_cast

He estado escribiendo código C y C ++ durante casi veinte años, pero hay un aspecto de estos idiomas que nunca he entendido. Obviamente he usado moldes regulares, es decir,

MyClass *m = (MyClass *)ptr; 

en todas partes, pero parece haber otros dos tipos de moldes, y no sé la diferencia. ¿Cuál es la diferencia entre las siguientes líneas de código?

 MyClass *m = (MyClass *)ptr; MyClass *m = static_cast(ptr); MyClass *m = dynamic_cast(ptr); 

static_cast

static_cast se usa para casos en los que básicamente desea revertir una conversión implícita, con algunas restricciones y adiciones. static_cast no realiza comprobaciones de tiempo de ejecución. Esto debería usarse si sabe que se refiere a un objeto de un tipo específico y, por lo tanto, una verificación sería innecesaria. Ejemplo:

 void func(void *data) { // Conversion from MyClass* -> void* is implicit MyClass *c = static_cast(data); ... } int main() { MyClass c; start_thread(&func, &c) // func(&c) will be called .join(); } 

En este ejemplo, usted sabe que pasó un objeto MyClass y, por lo tanto, no hay ninguna necesidad de una verificación en tiempo de ejecución para garantizar esto.

dynamic_cast

dynamic_cast es útil cuando no sabes cuál es el tipo dynamic del objeto. Devuelve un puntero nulo si el objeto al que se hace referencia no contiene el tipo invocado como clase base (cuando se lanza a una referencia, se bad_cast una excepción bad_cast en ese caso).

 if (JumpStm *j = dynamic_cast(&stm)) { ... } else if (ExprStm *e = dynamic_cast(&stm)) { ... } 

No puede usar dynamic_cast si dynamic_cast (lanzado a una clase derivada) y el tipo de argumento no es polimórfico. Por ejemplo, el siguiente código no es válido porque Base no contiene ninguna función virtual:

 struct Base { }; struct Derived : Base { }; int main() { Derived d; Base *b = &d; dynamic_cast(b); // Invalid } 

Un “up-cast” (conversión a la clase base) siempre es válido con static_cast y dynamic_cast , y también sin ningún tipo de conversión, ya que un “up-cast” es una conversión implícita.

Elenco regular

Estos moldes también se denominan molde de estilo C. Un elenco de estilo C es básicamente idéntico a probar un rango de secuencias de moldes de C ++, y tomar el primer molde de C ++ que funciona, sin siquiera considerar el dynamic_cast . Huelga decir que esto es mucho más poderoso ya que combina todos los const_cast de const_cast , static_cast y reinterpret_cast , pero también es inseguro porque no usa dynamic_cast .

Además, los moldes de estilo C no solo te permiten hacer esto, sino que también te permiten lanzar de forma segura a una clase base privada, mientras que la secuencia static_cast “equivalente” te daría un error en tiempo de comstackción para eso.

Algunas personas prefieren los moldes de estilo C debido a su brevedad. Los uso solo para los moldes numéricos, y uso los moldes de C ++ apropiados cuando están involucrados los tipos definidos por el usuario, ya que proporcionan una verificación más estricta.

Estático estático

La conversión estática realiza conversiones entre tipos compatibles. Es similar al lanzamiento de estilo C, pero es más restrictivo. Por ejemplo, el elenco estilo C permitiría un puntero entero apuntar a un char.

 char c = 10; // 1 byte int *p = (int*)&c; // 4 bytes 

Dado que esto da como resultado un puntero de 4 bytes que apunta a 1 byte de memoria asignada, escribir en este puntero provocará un error de tiempo de ejecución o sobrescribirá la memoria adyacente.

 *p = 5; // run-time error: stack corruption 

A diferencia del elenco de estilo C, el elenco estático le permitirá al comstackdor verificar que los tipos de datos de puntero y de punta son compatibles, lo que permite al progtwigdor detectar esta asignación de puntero incorrecta durante la comstackción.

 int *q = static_cast(&c); // compile-time error 

Reinterpretar el reparto

Para forzar la conversión del puntero, de la misma manera que lo hace el molde de estilo C en el fondo, se usaría en su lugar el molde de reinterpretación.

 int *r = reinterpret_cast(&c); // forced conversion 

Este molde maneja las conversiones entre ciertos tipos no relacionados, como desde un tipo de puntero a otro tipo de puntero incompatible. Simplemente realizará una copia binaria de los datos sin alterar el patrón de bits subyacente. Tenga en cuenta que el resultado de tal operación de bajo nivel es específico del sistema y, por lo tanto, no es portátil. Se debe usar con precaución si no se puede evitar por completo.

Lanzamiento dynamic

Este solo se usa para convertir punteros de objetos y referencias a objetos en otros tipos de punteros o referencias en la jerarquía de herencia. Es el único molde que asegura que el objeto apuntado se puede convertir, realizando una comprobación en tiempo de ejecución de que el puntero se refiere a un objeto completo del tipo de destino. Para que esta verificación en tiempo de ejecución sea posible, el objeto debe ser polimórfico. Es decir, la clase debe definir o heredar al menos una función virtual. Esto se debe a que el comstackdor solo generará la información necesaria del tipo de tiempo de ejecución para dichos objetos.

Ejemplos de lanzamiento dynamic

En el ejemplo siguiente, un puntero MyChild se convierte en un puntero MyBase usando un molde dynamic. Esta conversión derivada a base tiene éxito porque el objeto secundario incluye un objeto base completo.

 class MyBase { public: virtual void test() {} }; class MyChild : public MyBase {}; int main() { MyChild *child = new MyChild(); MyBase *base = dynamic_cast(child); // ok } 

El siguiente ejemplo intenta convertir un puntero MyBase a un puntero MyChild. Como el objeto Base no contiene un objeto Child completo, esta conversión de puntero fallará. Para indicar esto, la conversión dinámica devuelve un puntero nulo. Esto proporciona una manera conveniente de comprobar si una conversión ha tenido éxito o no durante el tiempo de ejecución.

 MyBase *base = new MyBase(); MyChild *child = dynamic_cast(base); if (child == 0) std::cout < < "Null pointer returned"; 

Si se convierte una referencia en lugar de un puntero, el lanzamiento dynamic fracasará arrojando una excepción bad_cast. Esto necesita ser manejado usando una statement try-catch.

 #include  // … try { MyChild &child = dynamic_cast(*base); } catch(std::bad_cast &e) { std::cout < < e.what(); // bad dynamic_cast } 

Molde dynamic o estático

La ventaja de usar un molde dynamic es que le permite al progtwigdor verificar si una conversión ha tenido éxito o no durante el tiempo de ejecución. La desventaja es que hay una sobrecarga de rendimiento asociada con este control. Por esta razón, usar un molde estático hubiera sido preferible en el primer ejemplo, porque una conversión derivada a base nunca fallará.

 MyBase *base = static_cast(child); // ok 

Sin embargo, en el segundo ejemplo, la conversión puede tener éxito o fallar. Fallará si el objeto MyBase contiene una instancia de MyBase y tendrá éxito si contiene una instancia de MyChild. En algunas situaciones, esto puede no conocerse hasta el tiempo de ejecución. Cuando este es el caso, el lanzamiento dynamic es una mejor opción que el lanzamiento estático.

 // Succeeds for a MyChild object MyChild *child = dynamic_cast(base); 

Si la conversión de base a derivada se hubiera realizado utilizando un modelo estático en lugar de un lanzamiento dynamic, la conversión no habría fallado. Habría devuelto un puntero que hacía referencia a un objeto incompleto. La desreferenciación de dicho puntero puede conducir a errores de tiempo de ejecución.

 // Allowed, but invalid MyChild *child = static_cast(base); // Incomplete MyChild object dereferenced (*child); 

Const cast

Este se usa principalmente para agregar o eliminar el modificador const de una variable.

 const int myConst = 5; int *nonConst = const_cast(&myConst); // removes const 

Aunque const cast permite cambiar el valor de una constante, hacerlo sigue siendo un código no válido que puede causar un error de tiempo de ejecución. Esto podría ocurrir, por ejemplo, si la constante estuviera ubicada en una sección de memoria de solo lectura.

 *nonConst = 10; // potential run-time error 

Const cast se usa principalmente cuando hay una función que toma un argumento de puntero no constante, aunque no modifique el puntero.

 void print(int *p) { std::cout < < *p; } 

La función puede pasar una variable constante mediante el uso de un molde constante.

 print(&myConst); // error: cannot convert // const int* to int* print(nonConst); // allowed 

Fuente y más explicaciones

Debería mirar el artículo C ++ Programming / Type Casting .

Contiene una buena descripción de todos los diferentes tipos de elenco. Lo siguiente tomado del enlace de arriba:

const_cast

const_cast (expresión) El const_cast <> () se usa para agregar / eliminar const (ness) (o volátil-ness) de una variable.

static_cast

static_cast (expresión) static_cast <> () se usa para transmitir entre los tipos enteros. ‘por ejemplo’ char-> largo, int-> corto etc.

El lanzamiento estático también se usa para lanzar punteros a tipos relacionados, por ejemplo, fundir vacío * al tipo apropiado.

dynamic_cast

La conversión dinámica se usa para convertir punteros y referencias en tiempo de ejecución, generalmente con el propósito de convertir un puntero o una referencia hacia arriba o hacia abajo en una cadena de herencia (jerarquía de herencia).

dynamic_cast (expresión)

El tipo de destino debe ser un puntero o tipo de referencia, y la expresión debe evaluarse como un puntero o referencia. La conversión dinámica funciona solo cuando el tipo de objeto al que se refiere la expresión es compatible con el tipo de destino y la clase base tiene al menos una función de miembro virtual. Si no es así, y el tipo de expresión que se lanza es un puntero, se devuelve NULL, si falla una conversión dinámica en una referencia, se lanza una excepción bad_cast. Cuando no falla, la conversión dinámica devuelve un puntero o referencia del tipo de destino al objeto al que se refiere la expresión.

reinterpretar_cast

Reinterpretar el molde simplemente arroja un tipo bitwise a otro. Cualquier puntero o tipo integral se puede convertir en cualquier otro elemento con reinterpretación, lo que permite un uso incorrecto. Por ejemplo, con el molde de reinterpretación, uno podría, inseguramente, lanzar un puntero entero a un puntero de cadena.

Evite usar moldes estilo C

Los moldes de estilo C son una mezcla de molde de const y reinterpretación, y es difícil de encontrar y reemplazar en tu código. Un progtwigdor de aplicaciones C ++ debe evitar el lanzamiento de estilo C.

FYI, creo que Bjarne Stroustrup es citado diciendo que los modelos de estilo C deben evitarse y que debes usar static_cast o dynamic_cast si es posible.

Preguntas frecuentes sobre el estilo C ++ de Barne Stroustrup

Toma ese consejo por lo que quieras. Estoy lejos de ser un gurú de C ++.

Los moldes de estilo C combinan const_cast, static_cast y reinterpret_cast.

Ojalá C ++ no tuviera moldes de estilo C. Los moldes de C ++ se destacan adecuadamente (como deberían, los moldes son normalmente indicativos de hacer algo malo) y distinguen correctamente entre los diferentes tipos de conversión que los lances realizan. También permiten que se escriban funciones de aspecto similar, por ejemplo boost :: lexical_cast, lo cual es bastante agradable desde una perspectiva de coherencia.

dynamic_cast tiene verificación de tipo de tiempo de ejecución y solo funciona con referencias y punteros, mientras que static_cast no ofrece verificación de tipo de tiempo de ejecución. Para obtener información completa, consulte el artículo de MSDN operador de static_cast .

dynamic_cast solo admite punteros y tipos de referencia. Devuelve NULL si el lanzamiento es imposible si el tipo es un puntero o arroja una excepción si el tipo es un tipo de referencia. Por lo tanto, dynamic_cast se puede usar para verificar si un objeto es de un tipo dado, static_cast no (puede terminar con un valor no válido).

Los moldes estilo C (y otros) se han cubierto en las otras respuestas.