Je suis un peu confus 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 une conversion statique lorsque les types peuvent être interprétés au moment de la compilation, d'où le mot static
. C’est le casting que le compilateur C++ utilise en interne pour les conversions implicites également.
reinterpret_cast
s sont applicables dans deux scénarios, convertir des types entiers en types de pointeur et inversement ou convertir un type de pointeur en un autre. L’idée générale que j’obtiens est que c’est impardonnable et que cela devrait être évité.
Là où je suis un peu confus, c’est un usage dont j’ai besoin, j’appelle C++ à partir de C et le code C doit conserver l’objet C++, de sorte qu’il contient en principe un void*
. Quel transtypage faut-il utiliser pour convertir entre void *
et le type Class?
J'ai vu utiliser à la fois static_cast
et reinterpret_cast
? Bien que, d'après ce que j'ai lu, il semble que static
soit préférable, car la distribution peut avoir lieu au moment de la compilation. Bien qu'il soit dit d'utiliser reinterpret_cast
pour convertir d'un type de pointeur à un autre?
La norme C++ garantit ce qui suit:
static_cast
l'ajout d'un pointeur vers et depuis void*
préserve l'adresse. En d'autres termes, a
, b
et c
désignent tous 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 que si vous transformez un pointeur en un type différent, puis reinterpret_cast
il revient au 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. (en pratique, il contiendra généralement la même adresse que a
et c
, mais ce n'est pas spécifié dans la norme et il se peut que ce ne soit pas le cas sur les ordinateurs dotés de systèmes de mémoire plus complexes.)
Pour le casting vers et depuis void*
, static_cast
devrait être préféré.
Un cas où reinterpret_cast
est nécessaire est lors de l'interfaçage 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 dans lequel 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;
}
Vous trouverez ci-dessous 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
_ représente, ne l'utilisez pas. Si vous en aurez besoin à l'avenir, vous 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 certains calculs car les deux nombres ont une représentation en bits différente. C’est ce que static_cast
signifie.
Par contre, lorsque vous appelez reinterpret_cast
, la CPU n’appelle aucun calcul. Il traite simplement 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 suppression du 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 en raison d'un seul ordre raison (endianness). Mais c’est souvent étonnamment la meilleure raison de l’utiliser. Imaginons l'exemple: vous devez lire un numéro binaire 32 bits à partir d'un fichier et vous savez qu'il est big endian. Votre code doit être générique et fonctionner correctement sur les systèmes big endian (certains ARM, par exemple) et little endian (x86, par exemple). Donc, vous devez vérifier l'ordre des octets. Il est bien connu au moment de la compilation, vous pouvez donc écrire la fonction Vous pouvez écrire une fonction pour y parvenir:constexpr
:
_/*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
en mémoire pourrait être _0000'0000'0000'0001
_ (grand) ou _0000'0001'0000'0000
_ (petit endian). Après avoir réinterprété-transtypé l'octet sous p
, le pointeur pourrait être respectivement _0000'0000
_ ou _0000'0001
_. Si vous utilisez le transtypage statique, ce sera toujours _0000'0001
_, quelle que soit la finalité utilisée.
EDIT:
Dans la première version, la fonction exemple _is_little_endian
_ était constexpr
. Il compile bien sur le dernier gcc (8.3.0) mais la norme dit que c'est illégal. Le compilateur Clang refuse de le compiler (ce qui est correct).
La signification de _reinterpret_cast
_ n'est pas définie par la norme C++. Par conséquent, en théorie, un _reinterpret_cast
_ pourrait bloquer votre programme. En pratique, les compilateurs essaient de faire ce que vous attendez, à savoir d’interpréter les éléments de ce que vous transmettez comme s’ils correspondaient au type que vous souhaitez utiliser. Si vous savez ce que les compilateurs que vous allez utiliser font avec _reinterpret_cast
_, vous pouvez l’utiliser, mais dire que c’est portable serait mentir.
Dans le cas que vous décrivez, et pratiquement dans tous les cas où vous pourriez envisager _reinterpret_cast
_, vous pouvez utiliser _static_cast
_ ou une autre alternative. La norme précise notamment ce que vous pouvez attendre de _static_cast
_ (§5.2.9):
Une valeur rvalue de type "pointeur sur cv void" peut être explicitement convertie en pointeur sur type d'objet. Une valeur de type pointeur sur objet convertie en "pointeur sur cv void" et revenant au type de pointeur d'origine aura sa valeur d'origine.
Donc, pour votre cas d'utilisation, il semble assez clair que le comité de normalisation vous a demandé d'utiliser _static_cast
_.
Reinterpret_cast peut être utilisé si vous souhaitez appliquer des opérations au niveau des bits aux flottants (IEEE 754). Un exemple de ceci est le truc Fast Inverse Root-Root:
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écale vers la droite et le soustrait d'une constante, divisant ainsi par deux et annulant l'exposant. Une fois reconvertie en float, elle est soumise à une itération de Newton-Raphson pour rendre cette approximation plus précise:
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;
}
Ceci a été écrit à l'origine en C, donc utilise les casts en C, mais le cast analogue en C++ est le reinterpret_cast.
Vous pouvez utiliser reinterprete_cast pour vérifier l'héritage au moment de la compilation.
Regardez ici: tilisation de reinterpret_cast pour vérifier l’héritage lors de la compilation
D'abord, vous avez 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 comme 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
BRIEF: cela signifie que la même mémoire est utilisée sous un type différent. Ainsi, vous pouvez convertir des représentations binaires de floats de type int, comme ci-dessus, en float. 0x80000000 est par exemple -0 (la mantisse et l'exposant sont nuls mais le signe, le msb, est 1. Cela fonctionne également pour les doubles et les doubles longs.
OPTIMISER: Je pense que reinterpret_cast serait optimisé dans de nombreux compilateurs, alors que la conversion en C est faite par pointerarithmétique (la valeur doit être copiée dans la mémoire, car les pointeurs ne pourraient pas pointer vers les registres cpu).
REMARQUE: dans les deux cas, vous devez enregistrer la valeur convertie dans une variable avant la conversion! Cette macro pourrait aider:
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
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 écrit un casting simple et sûr à l'aide de modèles. Notez que cette solution ne garantit pas de lancer des pointeurs sur une fonction.