c++ - reinterpret_cast - Reparto regular vs. static_cast vs. dynamic_cast




dynamic_cast static_cast (6)

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;

Por todas partes, pero parece que hay 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);

Reparto estático

El reparto estático realiza conversiones entre tipos compatibles. Es similar al elenco de estilo C, pero es más restrictivo. Por ejemplo, la conversión de estilo C permitiría que un puntero entero apunte a un carácter.

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 la memoria asignada, escribir en este puntero causará un error en tiempo de ejecución o sobrescribirá alguna memoria adyacente.

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

En contraste con la conversión de estilo C, la conversión estática permitirá que el compilador compruebe 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 elenco

Para forzar la conversión del puntero, de la misma forma que lo hace la conversión de estilo C en el fondo, se utilizará la conversión de reinterpretación en su lugar.

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

Esta conversión maneja las conversiones entre ciertos tipos no relacionados, como de 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 una operación de tan bajo nivel es específico del sistema y, por lo tanto, no es portátil. Debe utilizarse con precaución si no se puede evitar por completo.

Reparto dinámico

Este solo se utiliza para convertir punteros de objetos y referencias de objetos en otros tipos de punteros o referencias en la jerarquía de herencia. Es la única conversión que se asegura de que el objeto al que se apunta se puede convertir, al realizar una verificació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 de tipo de tiempo de ejecución necesaria para dichos objetos.

Ejemplos de reparto dinámico

En el siguiente ejemplo, un puntero de MyChild se convierte en un puntero de MyBase usando una conversión dinámica. Esta conversión derivada a base se realiza correctamente, 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 de MyBase en un puntero de MyChild. Dado que el objeto Base no contiene un objeto secundario 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 verificar si una conversión ha tenido éxito 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, la conversión dinámica fallará al lanzar una excepción bad_cast. Esto debe 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
}

Reparto dinámico o estático

La ventaja de usar una conversión dinámica es que le permite al programador verificar si una conversión ha tenido éxito durante el tiempo de ejecución. La desventaja es que hay una sobrecarga de rendimiento asociada con la realización de esta comprobación. Por esta razón, el uso de una conversión estática 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 ser conocido 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 una conversión estática en lugar de una conversión dinámica, la conversión no habría fallado. Habría devuelto un puntero que se refería a un objeto incompleto. La anulación de la referencia de un puntero de este tipo puede provocar errores en 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 la conversión constante permite cambiar el valor de una constante, hacerlo sigue siendo un código no válido que puede causar un error en el tiempo de ejecución. Esto podría ocurrir, por ejemplo, si la constante se encontraba en una sección de la memoria de solo lectura.

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

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

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

La función puede entonces pasar una variable constante utilizando una conversión constante.

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

print(nonConst); // allowed

Fuente y más explicaciones


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

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

En este ejemplo, sabe que pasó un objeto MyClass y, por lo tanto, no es necesario realizar 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 convertido como clase base (cuando se bad_cast en una referencia, se bad_cast una excepción de bad_cast en ese caso).

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

No puede usar dynamic_cast si realiza una conversión descendente (conversión 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
}

Una "conversión ascendente" (conversión a la clase base) siempre es válida con static_cast y dynamic_cast , y también sin ninguna conversión, ya que una "conversión ascendente" es una conversión implícita.

Reparto regular

Estos moldes también se llaman reparto de estilo C. Un reparto de estilo C es básicamente idéntico a probar una serie de secuencias de lanzamientos de C ++, y tomar el primer lanzamiento de C ++ que funcione, sin tener en cuenta nunca dynamic_cast . No hace falta decir que esto es mucho más poderoso, ya que combina todos los const_cast , static_cast y reinterpret_cast , pero también es inseguro, porque no usa dynamic_cast .

Además, los lanzamientos de estilo C no solo le permiten hacer esto, sino que también le permiten emitir de forma segura a una clase base privada, mientras que la secuencia "equivalente" de static_cast le daría un error de tiempo de compilación.

Algunas personas prefieren moldes de estilo C debido a su brevedad. Los uso solo para conversiones numéricas, y utilizo las conversiones de C ++ apropiadas cuando se trata de tipos definidos por el usuario, ya que proporcionan una comprobación más estricta.


Evite usar moldes estilo C

Los modelos de estilo C son una mezcla de modelos const y reinterpretados, y es difícil encontrar y reemplazar tu código. Un programador de aplicaciones C ++ debería evitar el reparto de estilo C


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

Desearía que C ++ no tuviera modelos de estilo C Los lanzamientos de C ++ se destacan correctamente (como deberían; los lanzamientos son normalmente indicativos de hacer algo malo) y distinguen adecuadamente entre los diferentes tipos de conversión que realizan los lanzamientos. También permiten escribir funciones de aspecto similar, por ejemplo, boost :: lexical_cast, que es bastante bueno desde una perspectiva de coherencia.


dynamic_cast solo admite punteros y tipos de referencia. Devuelve NULL si la conversión es imposible si el tipo es un puntero o lanza 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 modelos de estilo C (y otros) han sido cubiertos en las otras respuestas.


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





casting