2009-02-21 7 views
331

Je suis 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 sont d'utiliser le cast statique quand les types peuvent être interprétés à la compilation d'où le mot static. C'est le cast que le compilateur C++ utilise en interne pour les conversions implicites également.Quand utiliser reinterpret_cast?

reinterpret_cast s sont applicables dans deux scénarios, convertir des types entiers en types de pointeurs et vice versa ou convertir un type de pointeur en un autre. L'idée générale que je reçois est que ceci est nonportable et devrait être évitée. Où je suis un peu confus est une utilisation dont j'ai besoin, j'appelle C++ à partir de C et le code C doit rester sur l'objet C++ donc, fondamentalement, il détient void*. Quel casting devrait être utilisé pour convertir entre le void * et le type de classe? J'ai vu l'utilisation des deux static_cast et reinterpret_cast? Bien que d'après ce que j'ai lu il semble que static est mieux que la distribution peut se produire au moment de la compilation? Bien qu'il indique d'utiliser reinterpret_cast pour convertir d'un type de pointeur à l'autre?

+2

'reinterpret_cast' ne se produit pas au moment de l'exécution. Ce sont deux déclarations à la compilation. De http://en.cppreference.com/w/cpp/language/reinterpret_cast: "A la différence de static_cast, mais comme const_cast, l'expression reinterpret_cast ne compile pas avec les instructions de la CPU, c'est simplement une directive de compilation qui indique au compilateur traiter la séquence de bits (représentation d'objet) d'expression comme si elle avait le type new_type. " –

+0

@HeretoLearn, est-il possible d'ajouter les morceaux de code pertinents du fichier * .c et * .cpp? Je pense que cela peut améliorer l'exposition de la question. – OrenIshShalom

Répondre

335

norme du C garantit:

static_cast ing un pointeur vers et à partir void* conserve l'adresse. Autrement dit, dans ce qui suit, a, b et c tous les points à 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 autre type, puis reinterpret_cast revenir 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 est non spécifiée. (en pratique, il contiendra généralement la même adresse que a et c, mais cela n'est pas spécifié dans la norme, et il peut ne pas être vrai sur des machines avec des systèmes de mémoire plus complexes.

Pour la coulée vers et depuis le vide *, static_cast devrait être préféré.

+16

J'aime le fait que 'b' est indéfini. Ça t'arrête de faire des bêtises avec ça. Si vous lancez quelque chose vers un autre type de pointeur, vous demandez des problèmes et le fait que vous ne pouvez pas en être dépendant vous rend plus prudent. Si vous aviez utilisé static_cast <> ci-dessus, quelle est l'utilisation du 'b'? –

+2

Je pensais que reinterpret_cast <> garantissait le même modèle de bits. (ce qui n'est pas la même chose qu'un pointeur valide vers un autre type). –

+1

@Martin - reinterpret_cast <> n'est pas garanti pour aboutir au même modèle de bits. "Le mappage effectué par reinterpret_cast <> est défini par l'implémentation." (C++ 03 5.3.10). Cependant, la norme note que «cela ne doit pas être surprenant». –

-14

Lisez le FAQ! Tenir des données C++ en C peut être risqué.

En C++, un pointeur vers un objet peut être converti en void * sans aucune conversion. Mais ce n'est pas vrai dans l'autre sens. Vous aurez besoin d'un static_cast pour récupérer le pointeur d'origine.

16

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 morceaux de ce que vous transmettez comme s'ils étaient le type dans lequel vous vous lancez. Si vous savez ce que les compilateurs que vous allez utiliser faire avec reinterpret_cast vous pouvez l'utiliser, mais 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 la norme a ceci à dire au sujet de ce que vous pouvez attendre de static_cast (§5.2.9):

An rvalue of type “pointer to cv void” can be explicitly converted to a pointer to object type. A value of type pointer to object converted to “pointer to cv void” and back to the original pointer type will have its original value.

Donc, pour votre cas d'utilisation, il semble assez clair que le comité de normalisation destiné à vous d'utiliser static_cast .

+5

Pas tout à fait planter votre programme. La norme offre quelques garanties sur reinterpret_cast. Juste pas autant que les gens attendent souvent. – jalf

+0

Eh bien, le reinterpret_cast lui-même ne tomberait probablement pas en panne, mais il pourrait renvoyer un peu faux le résultat que, lorsque vous essayez de l'utiliser, pourrait causer un accident. – flodin

+1

Pas si vous l'utilisez correctement. Autrement dit, reinterpret_cast de A à B à A est parfaitement sûr et bien défini. Mais la valeur de B n'est pas spécifiée, et oui, si vous comptez sur cela, de mauvaises choses pourraient arriver. Mais le casting lui-même est assez sûr, tant que vous ne l'avez utilisé que de la façon dont la norme le permet. ;) – jalf

115

Un cas où reinterpret_cast est nécessaire est l'interfaçage avec des types de données opaques. Cela se produit fréquemment dans les API de fournisseur sur lesquelles le programmeur n'a aucun contrôle. Voici un exemple artificiel où un fournisseur fournit une API pour le stockage et la récupération de données globales arbitraires:

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

Pour utiliser cette API, le programmeur doit jeter leurs données à VendorGlobalUserData et retour. 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 mise en œuvre instrumentale de l'API exemple:

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

Oui, c'est à peu près la seule utilisation significative de reinterpret_cast à laquelle je puisse penser. – jalf

+7

Cela peut être une question tardive, mais pourquoi l'API du fournisseur n'utilise-t-elle pas void * pour cela? – Xeo

+76

@Xeo parce que l'API du fournisseur est nulle. –

0
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 d'un simple casting en toute sécurité en utilisant des modèles . Notez que cette solution ne garantit pas l'envoi de pointeurs sur une fonction.

+0

Quoi? Pourquoi s'embêter? C'est précisément ce que 'reinterpret_cast' fait déjà dans cette situation:" Un pointeur d'objet peut être explicitement converti en un pointeur d'objet d'un type différent. [72] Lorsqu'un _prvalue_ 'v' du type pointeur d'objet est converti en pointeur d'objet tapez "pointer to _cv_' T' ", le résultat est' static_cast (static_cast (v)) '." - N3797. –

+0

En ce qui concerne la norme 'C++ 2003' je ne peux ** PAS ** trouver que' reinterpret_cast' fait 'static_cast (static_cast (v))' –

+0

OK, vrai, mais je me fous d'une version de 13 il y a des années, et la plupart des codeurs ne devraient pas non plus (comme c'est probable) ils peuvent l'éviter. Les réponses et les commentaires devraient vraiment refléter la dernière norme disponible, sauf indication contraire ... IMHO. Quoi qu'il en soit, je pense que le Comité a ressenti le besoin de l'ajouter explicitement après 2003. (car IIRC, c'était pareil en C++ 11) –

9

Une utilisation de reinterpret_cast est si vous voulez appliquer les opérations de bits pour (IEEE 754) flotte. Un exemple de cela est l'inverse rapide tour Racine carrée:

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

Il traite de la représentation binaire du flotteur comme un entier, la décale à droite et la soustrait d'une constante, la réduction de moitié de ce fait et niant l'exposant . Après la conversion de retour à un flotteur, il est soumis à une itération pour rendre cette approximation plus exacte Newton-Raphson:

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é initialement écrit en C, donc utilise C jette, mais la fonte du analogue C est le reinterpret_cast.

+1

'erreur: cast invalide d'une expression rvalue de type 'int64_t {aka long long int}' pour taper 'double &' reinterpret_cast ((reinterpret_cast (d) >> 1) + (1L << 61)) '- http://ideone.com/6S4ijc – Orwellophile

+0

Désolé, j'essayais de reproduire le code de la mémoire (très brumeuse!). Il a maintenant été remplacé par le code original correct. –

+0

La norme indique qu'il s'agit d'un comportement indéfini: http://en.cppreference.com/w/cpp/language/reinterpret_cast (sous "type aliasing") –

-3

Réponse rapide: utilisez static_cast s'il compile, sinon recourez à reinterpret_cast.

0

Tout d'abord vous avez des données dans un type spécifique comme int ici:

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

Ensuite, vous souhaitez accéder à la même variable que d'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 

BREVE: cela signifie que la mémoire est utilisée en même comme un type différent. Vous pouvez donc convertir des représentations binaires de flottants en type int comme ci-dessus en flottants. 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 les doubles longs.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 pourraient pas pointer vers les registres cpu-).

REMARQUE: Dans les deux cas, vous devez enregistrer la valeur casted dans une variable avant la distribution! Cette macro pourrait aider:

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

La réponse courte: Si vous ne savez pas ce que signifie reinterpret_cast, ne l'utilisez pas. Si vous en aurez besoin dans le futur, vous le saurez.

réponse complète:

Considérons types de numéros 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 binaire différente. C'est ce que signifie static_cast. Par contre, lorsque vous appelez reinterpret_cast, la CPU n'appelle 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 pointeur dereferecing) n'a rien à voir avec l'ancienne valeur dans la signification mathématique.

Exemple: Il est vrai que reinterpret_cast est pas portable pour une raison - l'ordre des octets (de boutisme). Mais c'est souvent la meilleure raison de l'utiliser. Imaginons l'exemple: vous devez lire le nombre binaire 32 bits du fichier, et vous savez qu'il est grand 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 sur la compilation afin que vous puissiez écrire la fonction constexpr:

constexpr bool is_little_endian() { 
    unsigned short x=0x0001; 
    auto p = reinterpret_cast<unsigned char*>(&x); 
    return *p != 0; 
} 

Explication: la représentation binaire de x dans la mémoire pourrait être 0000'0000'0000'0001 (grand) ou 0000'0001'0000'0000 (little endian). Après la réinterprétation de l'octet sous le pointeur p pourrait être respectivement 0000'0000 ou 0000'0001. Si vous utilisez le static-casting, il sera toujours 0000'0001, peu importe ce que l'endianness est utilisé.

Questions connexes