Quand utiliser reinterpret cast?
Je suis peu confondu avec l'applicabilité de reinterpret_cast
vs static_cast
. D'après ce que j'ai lu, les règles générales consistent à utiliser un cast statique lorsque les types peuvent être interprétés au moment de la compilation, d'où le mot static
. C'est le cast que le compilateur C++ utilise en interne pour les casts implicites également.
reinterpret_cast
s sont applicables dans deux scénarios, convertir des types entiers en types de pointeurs et vice versa ou pour convertir un type de pointeur en un autre. L'idée générale que je reçois est que c'est unportable et devrait être éviter.
Où je suis un peu confus est une utilisation dont j'ai besoin, j'appelle C++ à partir de C et le code C doit s'accrocher à l'objet c++ Donc fondamentalement il contient un void*
. Quel cast doit être utilisé pour convertir entre le void *
et le type de classe?
J'ai vu l'utilisation de deux static_cast
et reinterpret_cast
? Bien que d'après ce que j'ai lu, il semble que static
est meilleur car le casting peut se produire au moment de la compilation? Bien qu'il soit dit d'utiliser reinterpret_cast
pour convertir d'un type de pointeur à un autre?
10 réponses
La norme c++ garantit ce qui suit:
static_cast
ing un pointeur vers et depuis void*
conserve l'adresse. C'est-à-dire que dans ce qui suit, a, b et c pointent tous vers la même adresse:
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
reinterpret_cast
ne garantit que si vous lancez un pointeur vers un type différent, puis reinterpret_cast
vers le type d'origine , Vous obtenez la valeur d'origine. Donc, dans ce qui suit:
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
A et c contiennent la même valeur, mais la valeur de b n'est pas spécifiée. (dans la pratique, il sera contiennent généralement la même adresse que a et c, mais cela n'est pas spécifié dans la norme, et cela peut ne pas être vrai sur les machines avec des systèmes de mémoire plus complexes.)
Pour lancer vers et depuis void*, static_cast
devrait être préféré.
Un cas où reinterpret_cast
est nécessaire est lors de l'interface avec des types de données opaques. Cela se produit fréquemment dans les API du fournisseur sur lesquelles le programmeur n'a aucun contrôle. Voici un exemple artificiel où un fournisseur fournit une API pour stocker et récupérer des données globales arbitraires:
// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();
Pour utiliser cette API, le programmeur doit convertir ses données en VendorGlobalUserData
et inversement. static_cast
ne fonctionnera pas, il faut utiliser 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;
}
Voici une implémentation artificielle de L'exemple D'API:
// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
La réponse courte:
Si vous ne savez pas ce que reinterpret_cast
signifie, Ne l'utilisez pas. Si vous en aurez besoin à l'avenir, vous le saurez.
Réponse Complète:
Considérons les types de nombres de base.
Lorsque vous convertissez par exemple int(12)
en unsigned float (12.0f)
, votre processeur doit invoquer des calculs car les deux nombres ont une représentation de bits différente. C'est quoi static_cast
signifie.
D'autre part, lorsque vous appelez reinterpret_cast
l' CPU n'invoque aucun calcul. Il traite juste un ensemble de bits dans la mémoire comme s'il avait un autre type. Ainsi, lorsque vous convertissez int*
en float*
avec ce mot-clé, la nouvelle valeur (après le déréférencement du pointeur) n'a rien à voir avec l'ancienne valeur au sens mathématique.
exemple: Il est vrai que reinterpret_cast
n'est pas portable pour une raison-l'ordre des octets (endianness). Mais c'est souvent étonnamment, la meilleure raison de l'utiliser. Imaginons l'exemple: vous avez à lisez le numéro binaire 32 bits à partir du fichier, et vous savez que c'est big endian. Votre code doit être générique et fonctionne correctement sur les systèmes big endian (par exemple ARM) et little endian (par exemple x86). Donc, vous devez vérifier l'ordre des octets. Il est bien connu au moment de la compilation, donc vous pouvez écrire constexpr
function:
constexpr bool is_little_endian() {
std::uint16_t x=0x0001;
auto p = reinterpret_cast<std::uint8_t*>(&x);
return *p != 0;
}
Explication: la représentation binaire de x
dans la mémoire pourrait être 0000'0000'0000'0001
(gros) ou 0000'0001'0000'0000
(little endian). Après réinterpret-casting l'octet sous p
pointeur pourrait être respectivement 0000'0000
ou 0000'0001
. Si vous utilisez static-casting, il sera toujours 0000'0001
, peu importe ce que endianness est utilisé.
La signification de reinterpret_cast
n'est pas définie par la norme c++. Par conséquent, en théorie, un reinterpret_cast
pourrait planter votre programme. En pratique, les compilateurs essaient de faire ce que vous attendez, c'est-à-dire d'interpréter les bits de ce que vous transmettez comme s'ils étaient le type vers lequel vous lancez. Si vous connaissez les compilateurs que vous allez utiliser faire avec reinterpret_cast
vous pouvez l'utiliser, mais de là à dire que c'est portable serait mentir.
Pour le cas que vous décrivez, et à peu près tous les cas où vous pourriez envisager reinterpret_cast
, Vous pouvez utiliser static_cast
ou une autre alternative à la place. Entre autres choses la norme a ceci à dire sur ce que vous pouvez attendre de static_cast
(§5.2.9):
Une valeur R de type "pointeur vers CV void" peut être explicitement convertie en un pointeur vers un type d'objet. Une valeur de type pointeur vers objet convertie en "pointeur vers CV void" et retour au type de pointeur d'origine aura sa valeur d'origine.
Donc, pour votre cas, il semble assez clair que la normalisation Comité destiné à vous d'utiliser static_cast
.
Une utilisation de reinterpret_cast est si vous voulez appliquer des opérations au niveau du BIT aux flottants (IEEE 754). Un exemple de ceci était L'astuce de racine carrée Inverse rapide:
Https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
Il traite la représentation binaire du flottant comme un entier, le déplace vers la droite et le soustrait d'une constante, réduisant ainsi de moitié et annulant l'exposant. Après la conversion en flotteur, il est soumis à un Newton-Raphson itération pour rendre cette approximation plus exacte:
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;
}
Cela a été écrit à l'origine en C, donc utilise des conversions C, mais le cast c++ analogue est le reinterpret_cast.
Vous pouvez utiliser reinterprete_cast pour vérifier l'héritage au moment de la compilation.
Regardez ici:
Utiliser reinterpret_cast pour vérifier l'héritage au moment de la compilation
template <class outType, class inType>
outType safe_cast(inType pointer)
{
void* temp = static_cast<void*>(pointer);
return static_cast<outType>(temp);
}
J'ai essayé de conclure et j'ai écrit un cast sûr simple en utilisant des modèles. Notez que cette solution ne garantit pas la conversion de pointeurs sur une fonction.
Vous avez D'abord des données dans un type spécifique comme int ici:
int x = 0x7fffffff://==nan in binary representation
Ensuite, vous voulez accéder à la même variable qu'un autre type comme float: Vous pouvez choisir entre
float y = reinterpret_cast<float&>(x);
//this could only be used in cpp, looks like a function with template-parameters
Ou
float y = *(float*)&(x);
//this could be used in c and cpp
Bref: cela signifie que la même mémoire est utilisée comme un type différent. Vous pouvez donc Convertir les représentations binaires de flotteurs en tant que type int comme ci-dessus en flotteurs. 0x80000000 est -0 par exemple (la mantisse et l'exposant sont nuls mais le signe, le msb, est un. Cela fonctionne également pour les doubles et longs doubles.
OPTIMIZE: je pense que reinterpret_cast serait optimisé dans de nombreux compilateurs, tandis que le C-casting est fait par pointerarithmetic (la valeur doit être copiée dans la mémoire, car les pointeurs ne peuvent pas pointer vers les registres cpu).
NOTE: dans les deux cas, vous devez enregistrer la valeur casted dans une variable avant de la lancer! Cette macro pourrait aider:
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
Réponse rapide: Utilisez static_cast
s'il compile, sinon recourir à reinterpret_cast
.
Lisez la FAQ ! Conserver des données C++ en C peut être risqué.
En C++, un pointeur vers un objet peut être converti void *
sans les plâtres. Mais ce n'est pas vrai dans l'autre sens. Vous auriez besoin d'un static_cast
pour récupérer le pointeur d'origine.