static_cast - reinterpret_cast c++ ejemplos
¿Cuándo usar reinterpret_cast? (10)
¡Lee las FAQ ! Mantener los datos de C ++ en C puede ser riesgoso.
En C ++, un puntero a un objeto se puede convertir a void *
sin ninguna conversión. Pero no es verdad al revés. Necesitarías un static_cast
para recuperar el puntero original.
Estoy un poco confundido con la aplicabilidad de reinterpret_cast
vs static_cast
. Por lo que he leído, las reglas generales son utilizar la conversión estática cuando los tipos se pueden interpretar en tiempo de compilación, de ahí la palabra static
. Esta es la conversión que el compilador de C ++ usa internamente para conversiones implícitas también.
reinterpret_cast
s son aplicables en dos escenarios, convierten tipos enteros en tipos de punteros y viceversa o para convertir un tipo de puntero a otro. La idea general que tengo es que esto no es portátil y debería 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é modelo debe usarse 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 tiempo de compilación. ¿Aunque dice usar reinterpret_cast
para convertir de un tipo de puntero a otro?
El estándar C ++ garantiza lo siguiente:
static_cast
un puntero hacia y desde void*
conserva la dirección. Es decir, en lo siguiente, a, byc, todos apuntan a la misma dirección:
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
reinterpret_cast
solo garantiza que si lanza un puntero a un tipo diferente, y luego reinterpret_cast
lo devuelve al tipo original , obtendrá el valor original. Así que en lo siguiente:
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
a y c contienen el mismo valor, pero el valor de b no está especificado. (en la práctica, normalmente contendrá la misma dirección que a y c, pero eso no está especificado en el estándar y puede que no sea cierto en máquinas con sistemas de memoria más complejos).
Para lanzar desde y hacia el void *, se debe preferir static_cast
.
El significado de reinterpret_cast
no está definido por el estándar C ++. Por lo tanto, en teoría, un reinterpret_cast
podría bloquear su programa. En la práctica, los compiladores intentan hacer lo que esperas, que es interpretar los bits de lo que estás transmitiendo como si fueran el tipo al que estás lanzando. Si sabe qué hacen los compiladores que va a usar con reinterpret_cast
, puede usarlo, pero decir que es portátil sería mentira.
Para el caso que describe, y para cualquier caso en el que pueda considerar 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 tipo "puntero a cv void" se puede convertir explícitamente en un puntero a 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.
Por lo tanto, para su caso de uso, parece bastante claro que el comité de estandarización pretende que utilice static_cast
.
Podría usar reinterprete_cast para verificar la herencia en el momento de la compilación.
Mire aquí: usar reinterpret_cast para verificar la herencia en tiempo de compilación
Primero tienes algunos datos en un tipo específico como int aquí:
int x = 0x7fffffff://==nan in binary representation
Entonces desea acceder a la misma variable que a otro tipo como float: puede decidir entre
float y = reinterpret_cast<float&>(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 las representaciones binarias de flotadores como tipo int, como arriba, a flotadores. 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 compiladores, mientras que el c-casting se realiza mediante pointerarithmetic (el valor debe copiarse a la memoria, ya que los punteros no pueden apuntar a cpu registers).
NOTA: ¡En ambos casos, debe guardar el valor de conversión en una variable antes de realizar la conversión! Esta macro podría ayudar:
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
Respuesta rápida: use static_cast
si se compila, de lo contrario recurra a reinterpret_cast
.
Un caso cuando es necesario reinterpret_cast
es cuando se conecta con tipos de datos opacos. Esto ocurre con frecuencia en las API de proveedores sobre las cuales el programador no tiene control. Este es un ejemplo ideado donde 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 programador debe convertir sus datos a VendorGlobalUserData
y viceversa. static_cast
no funcionará, uno debe usar reinterpret_cast
:
// main.cpp
#include "vendor.hpp"
#include <iostream>
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<VendorGlobalUserData>(&u); // compile error
d1 = reinterpret_cast<VendorGlobalUserData>(&u); // ok
VendorSetUserData(d1);
// do other stuff...
// retrieve global data
VendorGlobalUserData d2 = VendorGetUserData();
MyUserData * p = 0;
// p = d2; // compile error
// p = static_cast<MyUserData *>(d2); // compile error
p = reinterpret_cast<MyUserData *>(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; }
Un uso de reinterpret_cast es si desea aplicar operaciones bitwise a (IEEE 754) flotantes. Un ejemplo de esto fue el truco de la raíz cuadrada de inversión rápida:
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 negando el exponente. Después de volver a convertirse en un flotador, se somete a una iteración de Newton-Raphson para 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 se escribió originalmente en C, por lo que utiliza conversiones de C, pero la conversión de C ++ análoga es reinterpret_cast.
La respuesta corta: si no sabes lo que significa reinterpret_cast
, no lo uses. Si lo necesitarás en el futuro, lo sabrás.
Respuesta completa:
Consideremos los tipos de números básicos.
Cuando convierte, por ejemplo, int(12)
a unsigned float (12.0f)
su procesador necesita invocar algunos cálculos, ya que ambos números tienen una representación de bits diferente. Esto es lo que representa static_cast
.
Por otro lado, cuando llama a reinterpret_cast
la CPU no invoca ningún cálculo. Solo trata un conjunto de bits en la memoria como si tuviera otro tipo. Por lo tanto, cuando convierte int*
a float*
con esta palabra clave, el nuevo valor (después del borrado de puntero) no tiene nada que ver con el valor antiguo en el significado matemático.
Ejemplo: Es cierto que reinterpret_cast
no es portátil debido a una razón: orden de bytes (endianness). Pero a menudo esta es sorprendentemente la mejor razón para usarla. Imaginemos el ejemplo: tienes que leer el número binario de 32 bits del archivo, y sabes que es big endian. Su código debe ser genérico y funciona correctamente en los sistemas big endian (por ejemplo, ARM) y little endian (por ejemplo, x86). Así que hay que comprobar el orden de los bytes. Es conocido en tiempo de compilación, por lo que puede escribir la función constexpr
:
constexpr bool is_little_endian() {
std::uint16_t x=0x0001;
auto p = reinterpret_cast<std::uint8_t*>(&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
(little endian). Después de reinterpretar la conversión, el byte debajo del puntero p
podría ser respectivamente 0000''0000
o 0000''0001
. Si utilizas la 0000''0001
estática, siempre será 0000''0001
, sin importar qué tipo de endianidad se esté utilizando.
template <class outType, class inType>
outType safe_cast(inType pointer)
{
void* temp = static_cast<void*>(pointer);
return static_cast<outType>(temp);
}
Intenté concluir y escribí un sencillo y seguro reparto usando plantillas. Tenga en cuenta que esta solución no garantiza la conversión de punteros en una función.