Quelles sont les utilisations appropriées de:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
Comment décide-t-on lequel utiliser dans quels cas spécifiques?
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
à 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_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 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_cast
est 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_cast
est 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_cast
ne 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.
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.
(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);
}
Cela pourrait aider si vous connaissez un petit peu d'internes ...
static_cast
static_cast
pour eux.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. 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.A&
à B&
.dynamic_cast
(Base*)
à (Derived*)
peut échouer si le pointeur n'est pas réellement de type dérivé.A*
à B*
, si la conversion est non valide, dynamic_cast retournera nullptr.A&
à B&
si la conversion est invalide, dynamic_cast lève une exception bad_cast.const_cast
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.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
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 ).
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);
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.
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++: