Cuándo usar reinterpret_cast?

Estoy un poco confundido con la aplicabilidad de reinterpret_cast vs static_cast . Por lo que he leído, las reglas generales son usar el molde estático cuando los tipos pueden interpretarse en tiempo de comstackción, de ahí la palabra static . Este es el elenco que el comstackdor de C ++ usa internamente para los moldes implícitos también.

reinterpret_cast s son aplicables en dos escenarios, convertir tipos enteros a tipos de puntero y viceversa o convertir un tipo de puntero a otro. La idea general que obtengo es que esto no es posible de importar y debe evitarse.

Donde estoy un poco confundido es un uso que necesito, estoy llamando a C ++ desde C y el código C necesita aferrarse al objeto C ++, así que básicamente contiene un void* . ¿Qué molde se debe usar para convertir entre el void * y el tipo de clase?

He visto el uso de static_cast y reinterpret_cast ? Aunque por lo que he estado leyendo parece que la static es mejor, ya que el reparto puede ocurrir en el momento de la comstackción. ¿Aunque dice usar reinterpret_cast para convertir de un tipo de puntero a otro?

El estándar C ++ garantiza lo siguiente:

static_cast ing un puntero static_cast desde void* conserva la dirección. Es decir, a continuación, a, byc apuntan a la misma dirección:

 int* a = new int(); void* b = static_cast(a); int* c = static_cast(b); 

reinterpret_cast solo garantiza que si lanzas un puntero a un tipo diferente y luego lo vuelves a interpretar de nuevo al tipo original , obtienes el valor original. Entonces en lo siguiente:

 int* a = new int(); void* b = reinterpret_cast(a); int* c = reinterpret_cast(b); 

ayc contienen el mismo valor, pero el valor de b no está especificado. (en la práctica, generalmente contendrá la misma dirección que a y c, pero eso no está especificado en el estándar, y puede no ser cierto en máquinas con sistemas de memoria más complejos).

Para static_cast desde y hacia void *, se debe preferir static_cast .

Un caso cuando reinterpret_cast es necesario es cuando se interactúa con tipos de datos opacos. Esto ocurre con frecuencia en las API de proveedores sobre las cuales el progtwigdor no tiene control. Aquí hay un ejemplo inventado en el que un proveedor proporciona una API para almacenar y recuperar datos globales arbitrarios:

 // vendor.hpp typedef struct _Opaque * VendorGlobalUserData; void VendorSetUserData(VendorGlobalUserData p); VendorGlobalUserData VendorGetUserData(); 

Para usar esta API, el progtwigdor debe VendorGlobalUserData sus datos a VendorGlobalUserData y viceversa. static_cast no funcionará, se debe usar reinterpret_cast :

 // main.cpp #include "vendor.hpp" #include  using namespace std; struct MyUserData { MyUserData() : m(42) {} int m; }; int main() { MyUserData u; // store global data VendorGlobalUserData d1; // d1 = &u; // compile error // d1 = static_cast(&u); // compile error d1 = reinterpret_cast(&u); // ok VendorSetUserData(d1); // do other stuff... // retrieve global data VendorGlobalUserData d2 = VendorGetUserData(); MyUserData * p = 0; // p = d2; // compile error // p = static_cast(d2); // compile error p = reinterpret_cast(d2); // ok if (p) { cout << p->m << endl; } return 0; } 

A continuación se muestra una implementación artificial de la API de muestra:

 // vendor.cpp static VendorGlobalUserData g = 0; void VendorSetUserData(VendorGlobalUserData p) { g = p; } VendorGlobalUserData VendorGetUserData() { return g; } 

La respuesta corta: si no sabes lo que reinterpret_cast significa, no lo uses. Si lo necesitará en el futuro, lo sabrá.

Respuesta completa:

Consideremos los tipos básicos de números.

Cuando convierte, por ejemplo, int(12) en unsigned float (12.0f) su procesador necesita invocar algunos cálculos ya que ambos números tienen una representación de bit diferente. Esto es lo que static_cast representa.

Por otro lado, cuando llamas a reinterpret_cast la CPU no invoca ningún cálculo. Simplemente trata un conjunto de bits en la memoria como si tuviera otro tipo. Entonces, cuando convierte int* en float* con esta palabra clave, el nuevo valor (después del desreferenciado del puntero) no tiene nada que ver con el valor anterior en el significado matemático.

Ejemplo: Es cierto que reinterpret_cast no es portátil por una razón: orden de bytes (endianness). Pero esta es a menudo la mejor razón para usarlo. Imaginemos el ejemplo: tiene que leer el número binario de 32 bits del archivo, y sabe que es Big Endian. Su código tiene que ser genérico y funciona correctamente en sistemas big endian (por ejemplo, ARM) y little endian (por ejemplo, x86). Entonces debes verificar la orden de bytes. Es bien conocido en tiempo de comstackción para que pueda escribir la función constexpr :

 constexpr bool is_little_endian() { std::uint16_t x=0x0001; auto p = reinterpret_cast(&x); return *p != 0; } 

Explicación: la representación binaria de x en la memoria podría ser 0000'0000'0000'0001 (grande) o 0000'0001'0000'0000 (pequeño endian). Después de reinterpret-casting, el byte debajo del puntero p podría ser, respectivamente, 0000'0000 o 0000'0001 . Si usa fundición estática, siempre será 0000'0001 , sin importar qué endianness se esté utilizando.

El significado de reinterpret_cast no está definido por el estándar C ++. Por lo tanto, en teoría, una reinterpret_cast podría bloquear su progtwig. En la práctica, los comstackdores intentan hacer lo que esperas, que es interpretar los bits de lo que estás pasando como si fueran del tipo al que estás recurriendo. Si sabe qué hacen los comstackdores que va a utilizar con reinterpret_cast , puede usarlo, pero decir que es portable estaría mintiendo.

Para el caso que describa, y prácticamente cualquier caso en el que considere reinterpret_cast , puede usar static_cast o alguna otra alternativa. Entre otras cosas, el estándar tiene esto que decir acerca de lo que puede esperar de static_cast (§5.2.9):

Un valor de rval de tipo “puntero a cv void” se puede convertir explícitamente en un puntero al tipo de objeto. Un valor de tipo puntero a objeto convertido a “puntero a cv void” y de vuelta al tipo de puntero original tendrá su valor original.

Entonces, para su caso de uso, parece bastante claro que el comité de estandarización pretendía que usted use static_cast .

Un uso de reinterpret_cast es si desea aplicar operaciones en modo bit a flotantes (IEEE 754). Un ejemplo de esto fue el truco Fast Fusion Square-Root:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

Trata la representación binaria del flotador como un entero, lo desplaza hacia la derecha y lo resta de una constante, reduciendo a la mitad y anulando el exponente. Después de convertir nuevamente a un flotante, está sujeto a una iteración de Newton-Raphson para hacer que esta aproximación sea más exacta:

 float Q_rsqrt( float number ) { long i; float x2, y; const float threehalfs = 1.5F; x2 = number * 0.5F; y = number; i = * ( long * ) &y; // evil floating point bit level hacking i = 0x5f3759df - ( i >> 1 ); // what the deuce? y = * ( float * ) &i; y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed return y; } 

Esto fue escrito originalmente en C, por lo que usa C moldes, pero el molde análogo de C ++ es el reinterpret_cast.

Puede usar reinterprete_cast para verificar la herencia en tiempo de comstackción.
Mira aquí: Usar reinterpret_cast para verificar la herencia en tiempo de comstackción

 template  outType safe_cast(inType pointer) { void* temp = static_cast(pointer); return static_cast(temp); } 

Traté de concluir y escribí un simple molde seguro usando plantillas. Tenga en cuenta que esta solución no garantiza lanzar punteros a una función.

Primero tienes algunos datos en un tipo específico como int aquí:

 int x = 0x7fffffff://==nan in binary representation 

Luego desea acceder a la misma variable que a otro tipo, como float: puede decidir entre

 float y = reinterpret_cast(x); //this could only be used in cpp, looks like a function with template-parameters 

o

 float y = *(float*)&(x); //this could be used in c and cpp 

BREVE: significa que la misma memoria se usa como un tipo diferente. Por lo tanto, podría convertir representaciones binarias de flotantes como tipo int como arriba para flotar. 0x80000000 es -0 por ejemplo (la mantisa y el exponente son nulos, pero el signo, el msb, es uno. Esto también funciona para dobles y dobles largos.

OPTIMIZAR: Creo que reinterpret_cast se optimizaría en muchos comstackdores, mientras que el c-casting se hace mediante pointerarithmetic (el valor debe copiarse en la memoria, ya que los punteros no pueden apuntar a los cpu-registers).

NOTA: ¡En ambos casos, debe guardar el valor arrojado en una variable antes del lanzamiento! Esta macro podría ayudar:

 #define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; }) 

Respuesta rápida: use static_cast si se comstack, de lo contrario recurrir a reinterpret_cast .

Lee las preguntas frecuentes Mantener los datos de C ++ en C puede ser riesgoso.

En C ++, un puntero a un objeto se puede convertir a void * sin ningún molde. Pero no es verdad al revés. Necesitarías un static_cast para recuperar el puntero original.