c++ - style - Cast standard vs static_cast vs dynamic_cast
static_cast vs c style cast (6)
Cette question a déjà une réponse ici:
J'écris du code C et C ++ depuis près de vingt ans, mais il y a un aspect de ces langages que je n'ai jamais vraiment compris. J'ai évidemment utilisé des moulages réguliers
MyClass *m = (MyClass *)ptr;
partout, mais il semble y avoir deux autres types de moulages, et je ne connais pas la différence. Quelle est la différence entre les lignes de code suivantes?
MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);
Cast statique
La conversion statique effectue des conversions entre les types compatibles. Il est similaire à la distribution de style C, mais il est plus restrictif. Par exemple, le cast de style C permettrait à un pointeur entier de pointer vers un char.
char c = 10; // 1 byte
int *p = (int*)&c; // 4 bytes
Comme cela entraîne un pointeur de 4 octets pointant sur 1 octet de mémoire allouée, l'écriture sur ce pointeur provoquera une erreur d'exécution ou écrasera une mémoire adjacente.
*p = 5; // run-time error: stack corruption
Contrairement à la distribution de type C, la distribution statique permettra au compilateur de vérifier que les types de données pointeur et pointee sont compatibles, ce qui permet au programmeur d'intercepter cette affectation de pointeur incorrecte lors de la compilation.
int *q = static_cast<int*>(&c); // compile-time error
Réinterpréter la distribution
Pour forcer la conversion du pointeur, de la même manière que le cast de style C en arrière-plan, le cast de réinterprétation sera utilisé à la place.
int *r = reinterpret_cast<int*>(&c); // forced conversion
Ce transtypage gère les conversions entre certains types non liés, par exemple d'un type de pointeur à un autre type de pointeur incompatible. Il effectuera simplement une copie binaire des données sans modifier le modèle de bits sous-jacent. Notez que le résultat d'une telle opération de bas niveau est spécifique au système et n'est donc pas portable. Il doit être utilisé avec précaution s'il ne peut pas être complètement évité.
Cast dynamique
Celui-ci est seulement utilisé pour convertir des pointeurs d'objet et des références d'objet en d'autres types de pointeur ou de référence dans la hiérarchie d'héritage. C'est la seule distribution qui s'assure que l'objet pointé puisse être converti, en vérifiant à l'exécution que le pointeur fait référence à un objet complet du type de destination. Pour que cette vérification de l'exécution soit possible, l'objet doit être polymorphe. Autrement dit, la classe doit définir ou hériter d'au moins une fonction virtuelle. Cela est dû au fait que le compilateur génère uniquement les informations de type à l'exécution requises pour ces objets.
Exemples de cast dynamiques
Dans l'exemple ci-dessous, un pointeur MyChild est converti en pointeur MyBase à l'aide d'un cast dynamique. Cette conversion dérivée en base réussit, car l'objet enfant inclut un objet Base complet.
class MyBase
{
public:
virtual void test() {}
};
class MyChild : public MyBase {};
int main()
{
MyChild *child = new MyChild();
MyBase *base = dynamic_cast<MyBase*>(child); // ok
}
L'exemple suivant tente de convertir un pointeur MyBase en pointeur MyChild. Étant donné que l'objet Base ne contient pas un objet enfant complet, cette conversion de pointeur échouera. Pour l'indiquer, la distribution dynamique renvoie un pointeur nul. Cela constitue un moyen pratique de vérifier si une conversion a réussi au cours de l'exécution.
MyBase *base = new MyBase();
MyChild *child = dynamic_cast<MyChild*>(base);
if (child == 0)
std::cout << "Null pointer returned";
Si une référence est convertie au lieu d'un pointeur, la distribution dynamique échouera en lançant une exception bad_cast. Cela doit être géré à l'aide d'une instruction try-catch.
#include <exception>
// …
try
{
MyChild &child = dynamic_cast<MyChild&>(*base);
}
catch(std::bad_cast &e)
{
std::cout << e.what(); // bad dynamic_cast
}
Cast dynamique ou statique
L'avantage d'utiliser un transtypage dynamique est qu'il permet au programmeur de vérifier si une conversion a réussi au cours de l'exécution. L'inconvénient est qu'il y a un surcoût de performance associé à cette vérification. Pour cette raison, l'utilisation d'une distribution statique aurait été préférable dans le premier exemple, car une conversion dérivée vers la base n'échouera jamais.
MyBase *base = static_cast<MyBase*>(child); // ok
Cependant, dans le second exemple, la conversion peut réussir ou échouer. Il échouera si l'objet MyBase contient une instance MyBase et il réussira s'il contient une instance MyChild. Dans certaines situations, cela peut ne pas être connu avant l'exécution. Lorsque c'est le cas, la distribution dynamique est un meilleur choix que la distribution statique.
// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);
Si la conversion base-à-dérivée avait été effectuée à l'aide d'un cast statique au lieu d'un cast dynamique, la conversion n'aurait pas échoué. Il aurait renvoyé un pointeur faisant référence à un objet incomplet. Le déréférencement d'un tel pointeur peut entraîner des erreurs d'exécution.
// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);
// Incomplete MyChild object dereferenced
(*child);
Fonte de Const
Celui-ci est principalement utilisé pour ajouter ou supprimer le modificateur const d'une variable.
const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const
Bien que const cast permette de modifier la valeur d'une constante, cela reste un code non valide pouvant entraîner une erreur d'exécution. Cela peut se produire par exemple si la constante se trouvait dans une section de mémoire morte.
*nonConst = 10; // potential run-time error
La distribution Const est utilisée principalement quand il y a une fonction qui prend un argument de pointeur non constant, même si elle ne modifie pas la pointe.
void print(int *p)
{
std::cout << *p;
}
La fonction peut ensuite être passée une variable constante en utilisant un cast const.
print(&myConst); // error: cannot convert
// const int* to int*
print(nonConst); // allowed
static_cast
static_cast
est utilisé pour les cas où vous voulez inverser une conversion implicite, avec quelques restrictions et additions. static_cast
aucune vérification à l'exécution. Cela devrait être utilisé si vous savez que vous vous référez à un objet d'un type spécifique, et donc une vérification serait inutile. Exemple:
void func(void *data) {
// Conversion from MyClass* -> void* is implicit
MyClass *c = static_cast<MyClass*>(data);
...
}
int main() {
MyClass c;
start_thread(&func, &c) // func(&c) will be called
.join();
}
Dans cet exemple, vous savez que vous avez passé un objet MyClass
et que vous MyClass
donc pas besoin d'une vérification de l'exécution pour vous en assurer.
dynamic_cast
dynamic_cast
est utile lorsque vous ne connaissez pas le type dynamique de l'objet. Il renvoie un pointeur NULL si l'objet référencé ne contient pas le type casted en tant que classe de base (lorsque vous lancez une référence, une exception bad_cast
est levée dans ce cas).
if (JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
...
} else if (ExprStm *e = dynamic_cast<ExprStm*>(&stm)) {
...
}
Vous ne pouvez pas utiliser dynamic_cast
si vous effectuez un downcast (cast vers une classe dérivée) et que le type d'argument n'est pas polymorphe. Par exemple, le code suivant n'est pas valide, car Base
ne contient aucune fonction virtuelle:
struct Base { };
struct Derived : Base { };
int main() {
Derived d; Base *b = &d;
dynamic_cast<Derived*>(b); // Invalid
}
Un "up-cast" (cast à la classe de base) est toujours valide avec static_cast
et dynamic_cast
, et aussi sans aucun cast, comme un "up-cast" est une conversion implicite.
Cast régulier
Ces moulages sont également appelés cast C-style. Une distribution de style C est fondamentalement identique à l'essai d'une gamme de séquences de distributions C ++, et à la prise en charge de la première distribution C ++, sans jamais tenir compte de dynamic_cast
. Inutile de dire que c'est beaucoup plus puissant car il combine tout const_cast
, static_cast
et static_cast
, mais c'est aussi dangereux, car il n'utilise pas dynamic_cast
.
De plus, les transtypages de type C vous permettent non seulement de faire cela, mais ils vous permettent également de static_cast
en toute sécurité vers une classe de base privée, alors que la séquence static_cast
"équivalente" vous donnerait une erreur de compilation pour cela.
Certaines personnes préfèrent les moulages de style C en raison de leur brièveté. Je les utilise uniquement pour les conversions numériques et j'utilise les distributions C ++ appropriées lorsque des types définis par l'utilisateur sont impliqués, car ils permettent une vérification plus stricte.
Les casts de type C fusionnent const_cast, static_cast et reinterpret_cast.
Je souhaite que C ++ n'ait pas de conversions en style C. Les jets en C ++ se détachent correctement (comme ils le devraient, les jets sont normalement indicatifs de faire quelque chose de mal) et distinguent correctement les différents types de conversions que les jets effectuent. Ils permettent également d'écrire des fonctions similaires, par exemple boost :: lexical_cast, ce qui est plutôt agréable du point de vue de la cohérence.
Pour info, je crois que Bjarne Stroustrup est cité comme disant que les casts de style C doivent être évités et que vous devriez utiliser static_cast ou dynamic_cast si possible.
FAQ de style C ++ de Barne Stroustrup
Prenez ce conseil pour ce que vous voulez. Je suis loin d'être un gourou C ++.
dynamic_cast
a une vérification du type d'exécution et ne fonctionne qu'avec des références et des pointeurs, alors que static_cast
n'offre pas de vérification de type à l'exécution. Pour plus d'informations, consultez l'article MSDN static_cast Operator .
dynamic_cast
ne supporte que les types pointeur et référence. Il renvoie NULL
si la distribution est impossible si le type est un pointeur ou déclenche une exception si le type est un type de référence. Par conséquent, dynamic_cast
peut être utilisé pour vérifier si un objet est d'un type donné, static_cast
ne peut pas (vous obtiendrez simplement une valeur invalide).
Les moulages de style C (et autres) ont été couverts dans les autres réponses.