c++ - reinterpret_cast - static_cast++




Quand doit-on utiliser static_cast, dynamic_cast, const_cast et reinterpret_cast? (5)

Cela peut aider si vous connaissez un petit peu d'internes ...

static_cast

  • Le compilateur C ++ sait déjà comment convertir des types de mise à l'échelle, tels que float to int. Utilisez static_cast pour eux.
  • Lorsque vous demandez au compilateur de convertir le type A type B , static_cast appelle le constructeur de B transmettant A tant que paramètre. Alternativement, A pourrait avoir un opérateur de conversion (c.-à-d. A::operator B() ). Si B n'a pas un tel constructeur ou A n'a pas d'opérateur de conversion, vous obtenez une erreur de compilation.
  • La conversion de A* à B* réussit toujours si A et B sont dans la hiérarchie d'héritage (ou void), sinon vous obtenez une erreur de compilation.
  • Gotcha : Si vous transformez le pointeur de base en pointeur dérivé, mais si l'objet réel n'est pas vraiment un type dérivé, vous ne recevez pas d' erreur. Vous obtenez un mauvais pointeur et très probablement une erreur de segmentation au moment de l'exécution. Même chose pour A& B& .
  • Gotcha : Cast de Derived to Base ou vice-versa crée une nouvelle copie! Pour les personnes venant de C # / Java, cela peut être une énorme surprise car le résultat est essentiellement un objet découpé créé à partir de Derived.

diffusion_dynamique

  • dynamic_cast utilise des informations de type à l'exécution pour déterminer si la conversion est valide. Par exemple, (Base*) à (Derived*) peut échouer si le pointeur n'est pas réellement de type dérivé.
  • Cela signifie que dynamic_cast est très coûteux par rapport à static_cast!
  • Pour A* à B* , si la conversion est non valide, dynamic_cast retournera nullptr.
  • Pour A& to B& si cast n'est pas valide, dynamic_cast lèvera une exception bad_cast.
  • Contrairement aux autres distributions, il existe une surcharge d'exécution.

const_cast

  • Bien que static_cast puisse faire de const à const, il ne peut pas en être autrement. Le const_cast peut faire les deux choses.
  • Un exemple d'utilisation pratique consiste à itérer dans un conteneur tel que set<T> qui ne retourne que ses éléments en tant que const, afin de vous assurer que vous ne modifiez pas sa clé. Cependant, si votre intention est de modifier les membres non-clés de l'objet, alors cela devrait aller. Vous pouvez utiliser const_cast pour supprimer constness.
  • Un autre exemple est lorsque vous souhaitez implémenter T& foo() ainsi que le const T& foo() . Pour éviter la duplication de code, vous pouvez appliquer const_cast pour renvoyer la valeur d'une fonction d'une autre.

réinterpréter_cast

  • Cela signifie en gros que ces octets sont pris à cet emplacement de la mémoire et considérés comme un objet donné.
  • Par exemple, vous pouvez charger 4 octets de float sur 4 octets de int pour voir à quoi ressemblent les bits dans float.
  • De toute évidence, si les données ne sont pas correctes pour le type, vous pouvez obtenir une erreur de segmentation.
  • Il n'y a pas de temps d'exécution supplémentaire pour cette distribution.

Quelles sont les utilisations appropriées de:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value C
  • type(value) transtypage de style fonction type(value)

Comment décide-t-on lequel utiliser dans quels cas spécifiques?


En plus des autres réponses précédentes, voici un exemple non static_caststatic_cast n'est pas suffisant pour que reinterpret_cast soit nécessaire. Supposons qu'il existe une fonction qui, dans un paramètre de sortie, renvoie des pointeurs sur des objets de classes différentes (qui ne partagent pas une classe de base commune). CoCreateInstance() est un exemple réel de cette fonction (voir le dernier paramètre, qui est en fait void** ). Supposons que vous demandiez une classe d'objet particulière à cette fonction, afin de connaître à l'avance le type du pointeur (ce que vous faites souvent pour les objets COM). Dans ce cas, vous ne pouvez pas transformer le pointeur de votre pointeur en void** avec static_cast : vous avez besoin de static_cast reinterpret_cast<void**>(&yourPointer) .

Dans du code:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

Cependant, static_cast fonctionne pour les pointeurs simples (pas les pointeurs à pointeurs). Le code ci-dessus peut donc être réécrit pour éviter reinterpret_cast (au prix d'une variable supplémentaire) de la manière suivante:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

Tandis que d’autres réponses décrivaient joliment toutes les différences entre les conversions C ++, j’aimerais ajouter une note expliquant pourquoi vous ne devriez pas utiliser les conversions C-style (Type) var et Type(var) .

Pour les débutants en C ++, les casts de style C ressemblent à l'opération du sur-ensemble par rapport aux casts C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), réinterpréter_cast <> ()) et une préférence donnée aux casts C ++ . En fait, la distribution de style C est le sur-ensemble et est plus courte à écrire.

Le problème principal des conversions de style C est qu'elles cachent la véritable intention du développeur. Les distributions de style C peuvent effectuer pratiquement tous les types de diffusion, de la distribution normale à la sécurité effectuée par static_cast <> () et dynamic_cast <> () vers des conversions potentiellement dangereuses comme const_cast <> (), où le modificateur const peut être supprimé afin que les variables const peut être modifié et réinterpréter_cast <> () qui peut même réinterpréter des valeurs entières en pointeurs.

Voici l'échantillon.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

La raison principale pour laquelle les conversions en C ++ ont été ajoutées au langage était de permettre à un développeur de clarifier ses intentions - pourquoi il va faire ce casting. En utilisant des transts de style C parfaitement valables en C ++, vous rendez votre code moins lisible et plus sujet aux erreurs, en particulier pour les autres développeurs qui n'ont pas créé votre code. Donc, pour rendre votre code plus lisible et explicite, vous devriez toujours préférer les conversions C ++ aux converties de style C.

Voici une courte citation de l'ouvrage de Bjarne Stroustrup (l'auteur de C ++) intitulé La 4ème édition du langage de programmation C ++ - page 302.

Cette conversion de style C est beaucoup plus dangereuse que les opérateurs de conversion nommés, car la notation est plus difficile à identifier dans un programme volumineux et le type de conversion souhaité par le programmeur n’est pas explicite.


Utilisez dynamic_cast pour convertir des pointeurs / références dans une hiérarchie d'héritage.

Utilisez static_cast pour les conversions de types ordinaires.

Utilisez reinterpret_cast pour la reinterpret_cast à bas niveau des modèles de bits. Utilisez avec une extrême prudence.

Utilisez const_cast pour jeter const/volatile . Évitez cela sauf si vous êtes bloqué à l'aide d'une API const-incorrecte.


static_cast est le premier casting que vous devriez essayer d'utiliser. Il effectue des opérations telles que les conversions implicites entre types (tels que int pour float ou pointeur vers void* ), et il peut également appeler des fonctions de conversion explicites (ou implicites). Dans de nombreux cas, il n'est pas nécessaire d'indiquer explicitement static_cast , mais il est important de noter que la syntaxe T(something) équivaut à (T)something et doit être évitée (plus d'informations à ce sujet plus tard). Un T(something, something_else) est sûr, cependant, et garanti d'appeler le constructeur.

static_cast peut également static_cast hiérarchies d'héritage. Cela n'est pas nécessaire lors d'une conversion vers le haut (vers une classe de base), mais lors d'une conversion vers le bas, il peut être utilisé tant qu'il n'est pas transmis via virtual héritage virtual . Cependant, il ne vérifie pas et il s'agit d'un comportement non défini static_cast à static_cast dans une hiérarchie vers un type qui n'est pas réellement le type de l'objet.

const_cast peut être utilisé pour supprimer ou ajouter const à une variable; aucun autre casting C ++ n'est capable de le supprimer (pas même reinterpret_cast ). Il est important de noter que la modification d'une valeur anciennement const n'est indéfinie que si la variable d'origine est const ; si vous l'utilisez pour enlever à const une référence à quelque chose qui n'a pas été déclaré avec const , c'est sûr. Cela peut être utile lors de la surcharge de fonctions membres basées sur const , par exemple. Il peut également être utilisé pour ajouter const à un objet, par exemple pour appeler une surcharge de la fonction membre.

const_cast fonctionne également de manière similaire sur volatile , bien que ce soit moins courant.

dynamic_cast est presque exclusivement utilisé pour gérer le polymorphisme. Vous pouvez convertir un pointeur ou une référence en un type polymorphe en un autre type de classe (un type polymorphe a au moins une fonction virtuelle, déclarée ou héritée). Vous pouvez l'utiliser pour autre chose que de lancer vers le bas - vous pouvez lancer un côté ou même une autre chaîne. dynamic_cast cherchera l'objet désiré et le renverra si possible. Si ce n'est pas le cas, il retournera nullptr dans le cas d'un pointeur ou jettera std::bad_cast dans le cas d'une référence.

dynamic_cast a cependant certaines limites. Cela ne fonctionne pas s'il y a plusieurs objets du même type dans la hiérarchie d'héritage (le «diamant redouté») et que vous n'utilisez pas l'héritage virtual . De plus, il ne peut passer que par l'héritage public - il échouera toujours dans private héritage protected ou private . Toutefois, il s’agit rarement d’un problème, car de telles formes d’héritage sont rares.

reinterpret_cast est la distribution la plus dangereuse et doit être utilisée avec parcimonie. Il transforme un type directement en un autre, par exemple en transmettant la valeur d'un pointeur à un autre ou en stockant un pointeur dans un int , ou toutes sortes d'autres choses désagréables. En gros, la seule garantie que vous obtenez avec reinterpret_cast est que, normalement, si vous renvoyez le résultat au type d'origine, vous obtiendrez la même valeur (sauf si le type intermédiaire est plus petit que le type d'origine). Il y a un certain nombre de conversions que reinterpret_cast ne peut pas faire aussi. Il est principalement utilisé pour les conversions et les manipulations de bits particulièrement étranges, telles que la conversion d'un flux de données brutes en données réelles ou le stockage de données dans les bits inférieurs d'un pointeur aligné.

Les transtypages de style C et de fonction sont des transtypages utilisant (type)object ou type(object) , respectivement. Une distribution de style C est définie comme la première des actions suivantes qui réussit:

  • const_cast
  • static_cast (en ignorant les restrictions d'accès)
  • static_cast (voir ci-dessus), puis const_cast
  • reinterpret_cast
  • reinterpret_cast , puis const_cast

Il peut donc être utilisé en remplacement de certains transts dans certains cas, mais peut être extrêmement dangereux en raison de la possibilité de passer à une reinterpret_cast diffusion, et cette dernière doit être préférée lorsque une diffusion explicite est nécessaire, à moins que vous ne soyez sûr que static_cast réussisse ou reinterpret_cast va échouer. Même alors, considérons l'option plus longue et plus explicite.

Les distributions de style C ignorent également le contrôle d'accès lors de l'exécution de static_cast , ce qui signifie qu'elles ont la possibilité d'effectuer une opération qu'aucune autre conversion ne peut effectuer. Ceci est principalement une kludge, cependant, et dans mon esprit, c’est une raison supplémentaire d’éviter les moulages en C.





c++-faq