web-dev-qa-db-fra.com

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

Quelles sont les utilisations appropriées de:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Distribution de style C (type)value
  • Présentation fonctionnelle type(value)

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

2197
e.James

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

static_cast peut également transtyper des hiérarchies d'héritage. Il n'est pas nécessaire de lancer vers le haut (vers une classe de base), mais si vous versez vers le bas, vous pouvez l'utiliser tant qu'il n'est pas passé par l'héritage virtual. Cependant, cela ne fait pas de vérification et le comportement non défini de static_cast dans une hiérarchie vers un type qui n'est pas réellement le type de l'objet.


const_castpeut ê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 auparavant const n'est indéfinie que si la variable d'origine est const; si vous l'utilisez pour extraire la const d'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 fonction membre.

const_cast fonctionne également de la même façon sur volatile, bien que ce soit moins courant.


dynamic_castest 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 plus que simplement lancer vers le bas - vous pouvez lancer latéralement ou même remonter une autre chaîne. Le dynamic_cast cherchera l'objet désiré et le retournera 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 quelques limitations. 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 l'héritage protected ou private. Toutefois, il s’agit rarement d’un problème, car de telles formes d’héritage sont rares.


reinterpret_castest la distribution la plus dangereuse et doit être utilisé 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 une variable 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 redonnez le résultat au type d'origine, vous obtiendrez exactement la même valeur (maisnotsi le type intermédiaire est plus petit que le type d'origine. type). Il existe un certain nombre de conversions quereinterpret_castne peut pas effectuer également. 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 transtypes de style C et les transtypes de style fonction sont des conversions utilisant respectivement (type)object et type(object) et sont fonctionnellement équivalentes. Ils sont définis comme le premier des éléments suivants 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 s'avérer extrêmement dangereux en raison de la possibilité de devenir un reinterpret_cast, et ce dernier doit être préféré lorsque le transtypage explicite est nécessaire, sauf si vous êtes sûr que static_cast réussira 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 d'un 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.

2330
coppro

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 réinterpréter à bas niveau les 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.

303
Fred Larson

(Beaucoup d'explications théoriques et conceptuelles ont été données ci-dessus)

Vous trouverez ci-dessous quelques-uns des exemples pratiques lorsque j'ai utilisé static_cast, dynamic_cast, const_cast, reinterpret_cast.

(On y renvoie également pour comprendre l'explication: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

dynamic_cast:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
170
Sumit Arora

Cela pourrait 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 en B, static_cast appelle le constructeur de B en passant A en tant que param. Sinon, A pourrait avoir un opérateur de conversion (c'est-à-dire A::operator B()). Si B n'a pas ce constructeur, ou si A n'a pas d'opérateur de conversion, vous obtenez une erreur de compilation. 
  • La conversion de A* en 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 réellement dérivé, vous obtenez une erreur not. 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 new copy! 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.

dynamic_cast

  • 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& à B& si la conversion est invalide, dynamic_cast lève 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 en aller autrement. Le const_cast peut faire les deux choses.
  • Un exemple d'utilisation pratique est l'itération dans un conteneur tel que set<T> qui ne retourne que ses éléments en tant que const afin de s'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 const T& foo(). Pour éviter la duplication de code, vous pouvez appliquer const_cast pour renvoyer la valeur d'une fonction d'une autre.

reinterpret_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.
  • Évidemment, si les données ne sont pas correctes pour le type, vous pouvez obtenir un segfault.
  • Il n'y a pas de temps d'exécution supplémentaire pour cette distribution.
67
Shital Shah

Est-ce que this répond à votre question?

Je n'ai jamais utilisé reinterpret_cast et je me demande si le fait de rencontrer un cas qui en a besoin n'est pas une odeur de mauvais design. Dans la base de code sur laquelle je travaille, dynamic_cast est beaucoup utilisé. La différence avec static_cast est qu’un dynamic_cast effectue une vérification à l’exécution qui peut (être plus sûre) ou non (plus de temps système) être ce que vous voulez (voir msdn ).

12
andreas buykx

En plus des autres réponses précédentes, voici un exemple non évident où static_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). Un exemple réel de cette fonction est CoCreateInstance() (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 un pointeur vers votre pointeur en void** avec static_cast: vous avez besoin de 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 vers les 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);
12
Serge Rogatch

Tandis que d’autres réponses décrivaient joliment toutes les différences entre les distributions C++, je voudrais 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 castes de C++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), réinterpréter_cast <> ()) et une préférence donnée aux castes de C++ . En fait, la distribution de style C est le sur-ensemble et est plus courte à écrire.

Le problème principal des conversions en C est qu’elles cachent l’intention réelle du développeur. Les distributions de style C peuvent effectuer pratiquement tous les types de diffusion, à partir des distributions sûres effectuées par static_cast <> () et dynamic_cast <> () vers des conversions potentiellement dangereuses comme const_cast <> (), où le modificateur const peut être supprimé de sorte 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 transtypages 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 distribution de style C est beaucoup plus dangereuse que les opérateurs de conversion nommés parce que la notation est plus difficile à repérer dans un grand programme et que le type de conversion souhaité par le programmeur n’est pas explicite.

6
Timmy_A

Pour comprendre, considérons l'extrait de code ci-dessous:

struct Foo{};
struct Bar{};

int main(int argc, char** argv)
{
    Foo* f = new Foo;

    Bar* b1 = f;                              // (1)
    Bar* b2 = static_cast<Bar*>(f);           // (2)
    Bar* b3 = dynamic_cast<Bar*>(f);          // (3)
    Bar* b4 = reinterpret_cast<Bar*>(f);      // (4)
    Bar* b5 = const_cast<Bar*>(f);            // (5)

    return 0;
}

Seule la ligne (4) est compilée sans erreur. Seul reinterpret_cast peut être utilisé pour convertir un pointeur en objet en pointeur en un type d'objet non associé.

Il convient de noter, par exemple, que dynamic_cast échouera au moment de l'exécution, mais la compilation échouera également sur la plupart des compilateurs car il n'y a aucune fonction virtuelle dans la structure du pointeur en cours de conversion, ce qui signifie que dynamic_cast fonctionnera uniquement avec les pointeurs de classe polymorphes.

Quand utiliser la conversion C++:

  • Utilisez static_cast comme l’équivalent d’un transtypage de style C effectuant une conversion de valeur, ou lorsque nous devons explicitement convertir un pointeur d’une classe en superclasse.
  • Utilisez const_cast pour supprimer le qualificatif const. 
  • Utilisez reinterpret_cast pour effectuer des conversions non sécurisées de types de pointeur vers et à partir de nombres entiers et autres. Utilisez-le uniquement si nous savons ce que nous faisons et si nous comprenons les problèmes de repliement du spectre.
0
Pankaj Kumar Thapa