37 votes

casting via void * au lieu d'utiliser reinterpret_cast

Je suis en train de lire un livre et j'ai trouvé qu' reinterpret_cast ne devrait pas être utilisé directement, mais plutôt de la coulée de void* en combinaison avec d' 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 le cast. Je vous serais très reconnaissant si quelqu'un peut me donner une explication ou me pointer à la réponse.

Merci d'avance

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

26voto

Pavel Minaev Points 60647

Pour les types pour lesquels une telle distribution est autorisée (par exemple, si T1 est un module de type et T2 est unsigned char), l'approche avec static_cast est bien définie par la Norme.

D'autre part, reinterpret_cast est entièrement mise en œuvre définis par la seule garantie que vous obtenez pour elle est que vous pouvez jeter un type de pointeur pour tout autre type de pointeur, puis de nouveau, et vous obtiendrez la valeur d'origine; et aussi, vous pouvez le convertir en un type pointeur vers un type intégral assez grand pour contenir une valeur de pointeur (qui varie en fonction de la mise en œuvre, et ne doit plus exister du tout), puis de le jeter en arrière, et vous obtiendrez la valeur d'origine.

Pour être plus précis, je vais juste citer les parties pertinentes de la Norme, en mettant en évidence les parties importantes:

5.2.10[expr.réinterpréter.distribution]:

La cartographie réalisée par reinterpret_cast est mise en œuvre définies. [Note: il peut, ou non, de 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'une rvalue de type "pointeur vers T1" le type "pointeur vers T2" (où T1 et T2 sont des types d'objet et où l'alignement des exigences de T2 sont pas plus strictes que celles de T1) et le retour à son type d'origine des rendements de l'original de la valeur du pointeur, le résultat d'un tel pointeur de la conversion n'est pas spécifié.

Donc quelque chose comme ceci:

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

effectivement indéterminée.

Expliquant pourquoi static_cast d'œuvres est un peu plus compliqué. Voici le code ci-dessus réécrit pour utiliser static_cast je crois que c'est la garantie de toujours fonctionner 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 articles de la Norme qui, ensemble, m'amènent à conclure que la ci-dessus doivent être portable:

3.9[de base.types]:

Pour un objet quelconque (autre qu'une classe de base sous-objet) de la nacelle de type T, si oui ou non l'objet titulaire d'un permis de valeur de type T, le sous-jacent octets (1.7) faisant 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.

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

3.9.2[de base.composé]:

Les objets de cv qualifiés (3.9.3) ou cv-non qualifiés de type void* (pointeur sur void), peut être utilisé pour pointer vers des objets de type inconnu. Un void* doit être en mesure de tenir un pointeur d'objet. Un cv qualifiés ou cv-non qualifié (3.9.3) void* ont la même représentation et de l'alignement des exigences de cv qualifiés ou cv-sans restriction char*.

3.10[de base.lval]:

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

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

4.10[conv.ptr]:

Une rvalue de type "pointeur de cv T, où T est un type d'objet, peuvent être convertis en une rvalue de type "pointeur vers cv vide." Le résultat de la conversion d'un "pointeur de cv T" à un "pointeur de cv nul", pointe vers le début de l'emplacement de stockage où l'objet de type T réside, comme si l'objet est un objet dérivé de (1.8) de type T (qui est, non pas une classe de base sous-objet).

5.2.9[expr.statique.distribution]:

L'inverse de la conversion de la séquence (clause 4), autres que la lvalue-à-rvalue (4.1), array-topointer (4.2), la fonction de pointeur (4.3) et boolean (4.12) les conversions, peut être effectuée de manière explicite à l'aide static_cast.

[EDIT] d'autre part, nous avons ce bijou:

9.2[classe.mem]/17:

Un pointeur vers un POD-struct objet, convenablement converti à l'aide d'un reinterpret_cast, points à son premier membre (ou si le membre est un peu de champ, puis à l'unité dans laquelle il se trouve), et vice-versa. [Note: Il se peut donc être sans nom rembourrage à l'intérieur d'une GOUSSE de-struct objet, mais pas à son début, comme nécessaire afin d'obtenir l'alignement. ]

ce qui semble impliquer que, reinterpret_cast entre pointeurs en quelque sorte implique "même adresse". Aller à la figure.

6voto

curiousguy Points 2900

Il ne fait aucun doute que les deux formes sont bien définies, mais le libellé ne tient pas compte de cela.

Les deux formes fonctionneront dans la pratique.

reinterpret_cast est plus explicite sur l'intention et devrait être préféré.

3voto

La vraie raison pour cela est si c'est parce que de la façon dont C++ définit l'héritage, et en raison de membre de pointeurs.

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

Membre pointeurs sont vraiment un décalage dans une classe, afin de coulée est toujours une catastrophe à l'aide de C style.

Si vous avez multiplier hérité de deux objets virtuels qui ont aussi quelques pièces de béton, c'est aussi une catastrophe pour C style. C'est le cas de l'héritage multiple qui cause tous les problèmes, cependant, de sorte que vous ne devriez jamais souhaitez utiliser cela de toute façon.

Vraiment j'espère que vous n'utilisez jamais ces cas dans la première place. Aussi, si vous êtes à la coulée d'un lot qui est un autre signe que vous êtes bien dans votre conception.

Le seul moment où j'arrive à la fin de la 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, à tout moment vous voulez jeter quelque chose, commence à remettre en question votre conception parce que vous devriez être à la programmation de l'interface " la plupart du temps. Bien sûr, vous ne pouvez pas changer la façon dont la 3e partie des Api de travail de sorte que vous n'avez pas toujours beaucoup de choix.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X