Question

Je lis un livre et je trouve que reinterpret_cast ne doit pas être utilisé directement, mais plutôt d'annuler coulée * en combinaison avec static_cast:

T1 * p1=...
void *pv=p1;
T2 * p2= static_cast<T2*>(pv);

Au lieu de:

T1 * p1=...
T2 * p2= reinterpret_cast<T2*>(p1);

Cependant, je ne peux pas trouver une explication pourquoi est-ce mieux que la distribution directe. Je vous serais très reconnaissant si quelqu'un peut me donner une explication ou me indiquer la réponse.

Merci d'avance

p.s. Je sais ce qui est reinterpret_cast utilisé pour, mais je ne ai jamais vu qui est utilisé de cette façon

Était-ce utile?

La solution

Pour les types pour lesquels une telle distribution est autorisée (par exemple, si T1 est un type POD et T2 est unsigned char), l'approche par

static_cast est bien définie par la norme.

D'autre part, reinterpret_cast est entièrement défini par l'implémentation - la seule garantie que vous obtenez pour ce que vous pouvez jeter un type de pointeur à tout autre type de pointeur, puis de nouveau, et vous obtiendrez la valeur initiale; et aussi, vous pouvez jeter un type de pointeur sur un type intégral assez grand pour contenir une valeur de pointeur (qui varie en fonction de la mise en œuvre et les besoins existent pas du tout), puis le jeter en arrière, et vous obtiendrez la valeur initiale.

Pour être plus précis, je vais citer les parties pertinentes de la norme, mettant en évidence des éléments importants:

5.2.10 [expr.reinterpret.cast]:

  

La cartographie réalisée par reinterpret_cast est application définie . [Note:. Il pourrait, ou non, produire une représentation différente de la valeur d'origine] ... Un pointeur vers un objet peut être explicitement converti en un pointeur vers un objet de type différent) Sauf que la conversion d'un rvalue de type. « pointeur vers T1 » de type « pointeur vers T2 » (où T1 et T2 sont des types d'objet et où les exigences d'alignement de T2 ne sont plus sévères que celles de T1) et à son type d'origine donne la valeur de pointeur d'origine, le résultat d'une telle conversion de pointeur est non spécifiée .

Alors quelque chose comme ceci:

struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);

est effectivement non précisé.

Expliquer pourquoi les travaux static_cast est un peu plus délicat. Voici le code ci-dessus réécrite pour utiliser static_cast que je crois est garanti pour toujours travailler comme prévu par la norme:

struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);

Encore une fois, permettez-moi de citer les sections de la norme qui, ensemble, me conduisent à conclure que ci-dessus doivent être portables:

3.9 [basic.types]:

  

Pour tout objet (autre qu'une classe de base sous-objet) de type POD T, si l'objet ou non contient une valeur valide du type T, les octets sous-jacents (1,7) constituant l'objet peut être copié dans un tableau de char ou unsigned char. Si le contenu du tableau de char ou unsigned char est recopié dans l'objet, l'objet doit ensuite tenir sa valeur d'origine.

     

La représentation d'objet d'un objet de type T est la séquence de N unsigned char objets repris par l'objet de type T, où N est égal à sizeof (T).

3.9.2 [basic.compound]:

  

Objets de cv-qualifié (3.9.3) ou de type cv-non qualifié void* (pointeur sur void), peut être utilisé pour pointer vers des objets de type inconnu. Un void* doit pouvoir tenir tout pointeur d'objet. cv-qualifié ou non qualifié cv-(3.9.3) void* a les mêmes exigences de représentation et d'alignement en tant que char* cv-qualifié ou non qualifié cv-.

3.10 [basic.lval]:

  

Si un programme tente d'accéder à la valeur stockée d'un objet à travers une lvalue autre que celle de l'un des types suivants le comportement est indéfini):

     
      
  • ...
  •   
  • un type de char char ou unsigned .
  •   

4.10 [conv.ptr]:

  

Un rvalue de type « pointeur vers cv T », où T est un type d'objet, peut être converti en un rvalue de type « pointeur vers void cv. » Le résultat de la conversion d'un « pointeur vers cv T » à « pointeur vers des points void cv » au début de l'emplacement de stockage où l'objet de type T réside, comme si l'objet est un objet plus dérivée (1,8) de type T (à savoir, pas une classe de base sous-objet).

5.2.9 [expr.static.cast]:

  

L'inverse d'une séquence de conversion standard (article 4), autre que le lvalue à rvalue (4.1), un tableau-topointer (4.2), la fonction à aiguille (4,3), et booléen (4.12) conversions peuvent être effectuées en utilisant explicitement static_cast.

[EDIT] Par contre, nous avons ce petit bijou:

9,2 [class.mem] / 17:

  

Un pointeur vers un objet struct-POD, convenablement converti en utilisant un reinterpret_cast, pointe vers son élément initial (ou si ce membre est un champ binaire, puis à l'unité dans laquelle il se trouve), et vice versa. [Note: Il pourrait y avoir donc des remplissages sans nom dans un objet struct POD, mais pas à son début, si nécessaire pour obtenir un alignement approprié. ]

qui semble impliquer que reinterpret_cast entre les pointeurs implique en quelque sorte « même adresse ». Allez comprendre.

Autres conseils

Il n'y a pas le moindre doute que l'intention est que les deux formes sont bien définies, mais le libellé ne saisit pas cela.

Les deux formes fonctionneront dans la pratique.

reinterpret_cast est plus explicite au sujet de l'intention et doit être préféré.

La vraie raison en est ainsi parce que la façon dont C ++ définit l'héritage, et à cause de pointeurs membres.

Avec C, pointeur est à peu près juste une adresse, comme cela devrait être. En C ++, il doit être plus complexe en raison de certaines de ses caractéristiques.

Les pointeurs membres sont vraiment un décalage dans une classe, afin de les casting est toujours une catastrophe en utilisant le style C.

Si vous avez hérité de deux objets se multiplient virtuels qui ont aussi des éléments en béton, qui est aussi une catastrophe pour le style C. Tel est le cas dans l'héritage multiple qui provoque tous les problèmes, cependant, vous devriez donc jamais utiliser cette façon.

Vraiment, espérons-vous de ne jamais utiliser ces cas en premier lieu. En outre, si vous Jetant un lot qui est un autre signe que vous salissez dans votre conception.

La seule fois que je finis par coulée est avec les primitives dans les zones C ++ décide ne sont pas les mêmes, mais où il est évident qu'ils doivent être. Pour les objets réels, chaque fois que vous voulez lancer quelque chose, commencer à remettre en question votre conception parce que vous devez être « programmation à l'interface » la plupart du temps. Bien sûr, vous ne pouvez pas changer la façon dont les API 3ème partie travailler si vous n'avez pas toujours beaucoup de choix.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top