Pregunta Lanzamiento regular vs. static_cast vs. dynamic_cast [duplicado]


Esta pregunta ya tiene una respuesta aquí:

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<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);

1468
2017-08-26 13:20


origen


Respuestas:


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 verificaciones 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<MyClass*>(data);
  ...
}

int main() {
  MyClass c;
  start_thread(&func, &c)  // func(&c) will be called
      .join();
}

En este ejemplo, sabes que pasaste una MyClass objeto, 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 dinámico del objeto. Devuelve un puntero nulo si el objeto al que se hace referencia no contiene el tipo fundido como clase base (cuando lo convierte en una referencia, bad_cast se arroja una excepción en ese caso).

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

No puede utilizar dynamic_cast si downcast (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<Derived*>(b); // Invalid
}

Un "up-cast" (emitido a la clase base) siempre es válido con ambos static_cast y dynamic_cast, y también sin ningún elenco, 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 dynamic_cast. Huelga decir que esto es mucho más poderoso ya que combina todos const_cast, static_cast y reinterpret_cast, pero también es inseguro, porque no usa dynamic_cast.

Además, los moldes 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 el "equivalente" static_cast secuencia le daría un error en tiempo de compilació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.


1413
2017-08-10 13:50



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 compilador verificar que los tipos de datos de puntero y de punta son compatibles, lo que permite al programador detectar esta asignación de puntero incorrecta durante la compilación.

int *q = static_cast<int*>(&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<int*>(&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 dinámico

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 compilador solo generará la información necesaria del tipo de tiempo de ejecución para dichos objetos.

Ejemplos de lanzamiento dinámico

En el ejemplo siguiente, un puntero MyChild se convierte en un puntero MyBase usando un molde dinámico. 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<MyBase*>(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<MyChild*>(base);


if (child == 0) 
std::cout << "Null pointer returned";

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

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

Molde dinámico o estático

La ventaja de usar un molde dinámico es que le permite al programador 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<MyBase*>(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 dinámico es una mejor opción que el lanzamiento estático.

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

Si la conversión de base a derivada se hubiera realizado utilizando un modelo estático en lugar de un lanzamiento dinámico, 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<MyChild*>(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<int*>(&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


116
2017-08-26 13:28



Deberías mirar el artículo Programación en C ++ / Tipo de 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. 'p.ej.' char-> long, int-> short etc.

El lanzamiento estático también se usa para lanzar punteros a tipos relacionados, para   ejemplo void void * al tipo apropiado.

dynamic_cast

El lanzamiento dinámico se usa para convertir punteros y referencias en tiempo de ejecución,   generalmente con el propósito de lanzar un puntero o referencia hacia arriba o hacia abajo   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 el   la expresión debe evaluar a un puntero o referencia. Trabajos de fundición dinámica   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, y el tipo de expresión que se está emitiendo   es un puntero, se devuelve NULL, si un lanzamiento dinámico en una referencia   falla, se lanza una excepción bad_cast. Cuando no falla, dinámico   cast devuelve un puntero o referencia del tipo de destino al objeto   a qué expresión se refiere.

reinterpretar_cast

Reinterpretar el molde simplemente arroja un tipo bitwise a otro. Cualquier puntero   o el tipo integral se puede convertir a cualquier otro con el molde de reinterpretación,   permitiendo fácilmente el mal uso. Por ejemplo, con reinterpretar un molde   podría, inseguramente, lanzar un puntero entero a un puntero de cadena.


71
2017-09-19 17:30



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 programador de aplicaciones C ++ debe evitar el lanzamiento de estilo C.


23
2017-08-26 13:39



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 ++.


21
2017-08-26 13:38



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, p. boost :: lexical_cast, que es bastante agradable desde una perspectiva de coherencia.


10
2017-08-26 13:26



dynamic_casttiene 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 static_cast.


8
2018-02-05 17:10



dynamic_cast solo admite punteros y tipos de referencia. Vuelve NULL si el yeso 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 (simplemente terminará con un valor no válido).

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


7