tipos static_cast implicita funciones estatico ejemplo dynamic_cast datos conversion casteo c++ pointers casting c++-faq

static_cast - funciones de conversion en c++



¿Cuándo deben utilizarse static_cast, dynamic_cast, const_cast y reinterpret_cast? (7)

¿Cuáles son los usos adecuados de:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value C (estilo (type)value
  • type(value) conversión de estilo de función type(value)

¿Cómo se decide cuál usar en qué casos específicos?


¿Responde this tu pregunta?

Nunca he usado reinterpret_cast , y me pregunto si encontrar un caso que lo necesite no es un olor a mal diseño. En el código base trabajo en dynamic_cast se usa mucho. La diferencia con static_cast es que dynamic_cast realiza una comprobación en tiempo de ejecución que puede (más seguro) o no (más sobrecarga) ser lo que desea (ver msdn ).


Además de las otras respuestas hasta ahora, aquí hay un ejemplo obvio donde static_cast no es suficiente para que se necesite reinterpret_cast . Supongamos que hay una función que en un parámetro de salida devuelve los punteros a objetos de diferentes clases (que no comparten una clase base común). Un ejemplo real de dicha función es CoCreateInstance() (ver el último parámetro, que de hecho es void** ). Supongamos que solicita una clase particular de objeto de esta función, por lo que sabe de antemano el tipo para el puntero (que a menudo hace para los objetos COM). En este caso, no puede lanzar el puntero a su puntero en void** con static_cast : necesita reinterpret_cast<void**>(&yourPointer) .

En codigo:

#include <windows.h> #include <netfw.h> ..... INetFwPolicy2* pNetFwPolicy2 = nullptr; HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2), //static_cast<void**>(&pNetFwPolicy2) would give a compile error reinterpret_cast<void**>(&pNetFwPolicy2) );

Sin embargo, static_cast funciona para punteros simples (no punteros a punteros), por lo que el código anterior puede reescribirse para evitar reinterpret_cast (a un precio de una variable adicional) de la siguiente manera:

#include <windows.h> #include <netfw.h> ..... INetFwPolicy2* pNetFwPolicy2 = nullptr; void* tmp = nullptr; HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2), &tmp ); pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);


Mientras que otras respuestas describieron muy bien todas las diferencias entre los modelos de C ++, me gustaría agregar una breve nota de por qué no debe usar modelos de (Type) var C (Type) var y Type(var) .

Para los principiantes de C ++, los lanzamientos de estilo C parecen ser la operación de superconjunto sobre los lanzamientos de C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) y alguien podría preferirlos sobre los modelos de C ++ . De hecho, el reparto de estilo C es el superconjunto y más corto para escribir.

El principal problema de los modelos de estilo C es que ocultan la intención real del desarrollador del equipo. Los lanzamientos de estilo C pueden realizar virtualmente todos los tipos de lanzamientos, desde los lanzamientos normalmente seguros realizados por static_cast <> () y dynamic_cast <> () hasta los lanzamientos potencialmente peligrosos como const_cast <> (), donde el modificador de const puede eliminarse de manera que las variables const se puede modificar y reinterpret_cast <> () que incluso puede reinterpretar los valores enteros a los punteros.

Aquí está la muestra.

int a=rand(); // Random number. int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation. int* pa2=static_cast<int*>(a); // Compiler error. int* pa3=dynamic_cast<int*>(a); // Compiler error. int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo. *pa4=5; // Program crashes.

La razón principal por la que los lanzamientos de C ++ se agregaron al lenguaje fue para permitir que un desarrollador aclare sus intenciones, por qué va a hacer ese lanzamiento. Al usar modelos de estilo C que son perfectamente válidos en C ++, hace que su código sea menos legible y más propenso a errores, especialmente para otros desarrolladores que no crearon su código. Por lo tanto, para que su código sea más legible y explícito, siempre debe preferir los lanzamientos de C ++ sobre los de estilo C

Aquí hay una breve cita del libro de Bjarne Stroustrup (el autor de C ++) The C ++ Programming Language 4th edition - página 302.

Este reparto de estilo C es mucho más peligroso que los operadores de conversión nombrados porque la notación es más difícil de detectar en un programa grande y el tipo de conversión que pretende el programador no es explícito.


Podría ayudar si sabes un poco de partes internas ...

static_cast

  • El compilador de C ++ ya sabe cómo convertir tipos de escaladores, como float a int. Usa static_cast para ellos.
  • En el caso general de la conversión de tipo A a B , static_cast llama al constructor de B pasa A como parámetro. Si B no tiene dicho constructor, obtienes un error de tiempo de compilación.
  • La conversión de A* a B* siempre tiene éxito si A y B están en jerarquía de herencia (o nula), de lo contrario, se produce un error de compilación.
  • Gotcha : Si lanza el puntero de base al puntero derivado pero si el objeto real no es realmente un tipo derivado, entonces no obtiene el error. Obtienes un mal puntero y un error de seguridad en tiempo de ejecución. Lo mismo ocurre con A& to B& .
  • Gotcha : ¡Reparto de Derivado a Base o viceversa crea una nueva copia! Para las personas que vienen de C # / Java, esto puede ser una gran sorpresa.

dynamic_cast

  • dynamic_cast utiliza información de tipo de tiempo de ejecución para averiguar si la conversión es válida. Por ejemplo, (Base*) a (Derived*) puede fallar si el puntero no es realmente del tipo derivado.
  • Esto significa que dynamic_cast es muy caro en comparación con static_cast!
  • Para A* a B* , si el lanzamiento no es válido, dynamic_cast devolverá nullptr.
  • Para A& to B& si el lanzamiento no es válido, dynamic_cast lanzará la excepción bad_cast.
  • A diferencia de otros lanzamientos, hay una sobrecarga de tiempo de ejecución.

const_cast

  • Si bien static_cast puede hacer constantes, no puede ir de otra manera. El const_cast puede hacer ambas formas.
  • Un ejemplo en el que esto es útil es iterar a través de algún contenedor como el set<T> que solo devuelve sus elementos como const para asegurarse de que no cambie su clave. Sin embargo, si su intención es modificar los miembros no clave del objeto, entonces debería estar bien. Puedes usar const_cast para eliminar constness.
  • Otro ejemplo es cuando desea implementar T& foo() , así como const T& foo() . Para evitar la duplicación de código, puede aplicar const_cast para devolver el valor de una función de otra.

reinterpretar_cast

  • Básicamente, esto dice que tome estos bytes en esta ubicación de memoria y piense en él como un objeto dado.
  • Por ejemplo, puede cargar 4 bytes de float a 4 bytes de int para ver cómo se ven los bits en float.
  • Obviamente, si los datos no son correctos para el tipo, puede obtener segfault.
  • No hay sobrecarga de tiempo de ejecución para este elenco.

Utilice dynamic_cast para convertir punteros / referencias dentro de una jerarquía de herencia.

Utilice static_cast para conversiones de tipo ordinario.

Utilice reinterpret_cast para la reinterpret_cast de bajo nivel de patrones de bits. Utilizar con extrema precaución.

Use const_cast para desechar const/volatile . Evita esto a menos que estés atascado usando una API const-incorrect.


(Se ha dado mucha explicación teórica y conceptual arriba)

A continuación se muestran algunos ejemplos prácticos cuando usé static_cast , dynamic_cast , const_cast , reinterpret_cast .

(También hace referencia a esto para comprender la explicación: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData) { ...... // pData is a void* pData, // EventData is a structure e.g. // typedef struct _EventData { // std::string id; // std:: string remote_id; // } EventData; // On Some Situation a void pointer *pData // has been static_casted as // EventData* pointer EventData *evtdata = static_cast<EventData*>(pData); ..... }

dynamic_cast:

void DebugLog::OnMessage(Message *msg) { static DebugMsgData *debug; static XYZMsgData *xyz; if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){ // debug message } else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){ // xyz message } else/* if( ... )*/{ // ... } }

const_cast:

// *Passwd declared as a const const unsigned char *Passwd // on some situation it require to remove its constness const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16; // Read Bytes returns that 2 bytes got read. bool ByteBuffer::ReadUInt16(uint16& val) { return ReadBytes(reinterpret_cast<char*>(&val), 2); }


static_cast es el primer lanzamiento que debes intentar usar. Hace cosas como conversiones implícitas entre tipos (como int a float , o puntero a void* ), y también puede llamar a funciones de conversión explícitas (o implícitas). En muchos casos, indicar explícitamente static_cast no es necesario, pero es importante tener en cuenta que la sintaxis de T(something) es equivalente a (T)something y debe evitarse (más sobre esto más adelante). Sin embargo, una T(something, something_else) es segura y está garantizada para llamar al constructor.

static_cast también se puede convertir a través de jerarquías de herencia. No es necesario cuando se lanza hacia arriba (hacia una clase base), pero cuando se lanza hacia abajo se puede usar siempre y cuando no se haga mediante herencia virtual . Sin embargo, no verifica, y es un comportamiento indefinido para static_cast bajar una jerarquía a un tipo que no es realmente el tipo del objeto.

const_cast se puede usar para eliminar o agregar const a una variable; ningún otro C ++ cast es capaz de eliminarlo (ni siquiera reinterpret_cast ). Es importante tener en cuenta que modificar un valor anteriormente const está definido solo si la variable original es const ; Si lo usa para quitar la const de una referencia a algo que no se declaró con const , es seguro. Esto puede ser útil cuando se sobrecargan funciones miembro basadas en const , por ejemplo. También se puede usar para agregar const a un objeto, como para llamar a una sobrecarga de la función miembro.

const_cast también funciona de manera similar en volatile , aunque eso es menos común.

dynamic_cast se dynamic_cast casi exclusivamente para manejar el polimorfismo. Puede lanzar un puntero o una referencia a cualquier tipo polimórfico a cualquier otro tipo de clase (un tipo polimórfico tiene al menos una función virtual, declarada o heredada). Puede usarlo para algo más que lanzar hacia abajo: puede lanzar hacia los lados o incluso hasta otra cadena. El dynamic_cast buscará el objeto deseado y lo devolverá si es posible. Si no puede, devolverá nullptr en el caso de un puntero, o lanzará std::bad_cast en el caso de una referencia.

dynamic_cast tiene algunas limitaciones, sin embargo. No funciona si hay varios objetos del mismo tipo en la jerarquía de herencia (el llamado ''diamante temido'') y no está utilizando virtual herencia virtual . También solo puede pasar por herencia pública, siempre dejará de viajar por herencia protected o private . Sin embargo, esto rara vez es un problema, ya que tales formas de herencia son raras.

reinterpret_cast es el reparto más peligroso, y debe usarse con moderación. Convierte un tipo directamente en otro, como la conversión del valor de un puntero a otro, o el almacenamiento de un puntero en un int , o todo tipo de otras cosas desagradables. En gran parte, la única garantía que obtiene con reinterpret_cast es que normalmente si vuelve a convertir el resultado al tipo original, obtendrá el mismo valor exacto (pero no si el tipo intermedio es más pequeño que el tipo original). Hay una cantidad de conversiones que reinterpret_cast no puede hacer, también. Se usa principalmente para conversiones particularmente extrañas y manipulaciones de bits, como convertir un flujo de datos en bruto en datos reales, o almacenar datos en los bits bajos de un puntero alineado.

La conversión de estilo C y la conversión de estilo de función se realizan mediante el uso de (type)object o type(object) , respectivamente. Un reparto de estilo C se define como el primero de los siguientes que tiene éxito:

  • const_cast
  • static_cast (aunque ignora las restricciones de acceso)
  • static_cast (ver arriba), luego const_cast
  • reinterpret_cast
  • reinterpret_cast , luego const_cast

Por lo tanto, se puede usar como reemplazo de otros lanzamientos en algunos casos, pero puede ser extremadamente peligroso debido a la capacidad de convertirse en un reinterpret_cast , y este último debería ser preferido cuando se necesita un lanzamiento explícito, a menos que esté seguro de que static_cast tendrá éxito o reinterpret_cast fallará. Incluso entonces, considere la opción más larga, más explícita.

Los lanzamientos de estilo C también ignoran el control de acceso al realizar un static_cast , lo que significa que tienen la capacidad de realizar una operación que ningún otro lanzamiento puede. Sin embargo, esto es principalmente una confusión, y en mi opinión es solo otra razón para evitar los lanzamientos de estilo C.