Question

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, c'est-à-dire

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);
Était-ce utile?

La solution

static_cast

static_cast est utilisé dans les cas où vous souhaitez essentiellement annuler une conversion implicite, avec quelques restrictions et ajouts. static_cast n'effectue aucune vérification d'exécution.Ceci doit être utilisé si vous savez que vous faites référence à un objet d'un type spécifique et qu'une vérification serait donc 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 réussi un MyClass objet, et il n'est donc pas nécessaire de procéder à une vérification d'exécution pour s'en assurer.

dynamique_cast

dynamic_cast est utile lorsque vous ne savez pas quel est le type dynamique de l'objet.Il renvoie un pointeur nul si l'objet référencé ne contient pas le type converti en classe de base (lorsque vous convertissez en une référence, un bad_cast une exception 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 vers la classe de base) est toujours valide avec les deux static_cast et dynamic_cast, et également sans aucune conversion, car une "up-cast" est une conversion implicite.

Distribution régulière

Ces moulages sont également appelés moulages de style C.Un casting de style C est fondamentalement identique à l'essai d'une gamme de séquences de casts C++ et à la première distribution C++ qui fonctionne, sans jamais considérer dynamic_cast.Inutile de dire que c'est beaucoup plus puissant car il combine tous les const_cast, static_cast et reinterpret_cast, mais c'est aussi dangereux, car il n'utilise pas dynamic_cast.

De plus, les castings de style C vous permettent non seulement de le faire, mais ils vous permettent également de lancer en toute sécurité vers une classe de base privée, tandis que "l'équivalent" static_cast séquence 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 conversions C++ appropriées lorsque des types définis par l'utilisateur sont impliqués, car ils fournissent une vérification plus stricte.

Autres conseils

Casting statique

La conversion statique effectue des conversions entre types compatibles.Il est similaire au casting de style C, mais est plus restrictif.Par exemple, le cast de style C permettrait à un pointeur entier de pointer vers un caractère.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Étant donné que cela se traduit par un pointeur de 4 octets pointant vers 1 octet de mémoire allouée, l'écriture sur ce pointeur provoquera une erreur d'exécution ou écrasera une partie de la mémoire adjacente.

*p = 5; // run-time error: stack corruption

Contrairement au cast de style C, le cast statique permettra au compilateur de vérifier que les types de données pointeur et pointee sont compatibles, ce qui permet au programmeur de détecter cette affectation incorrecte du pointeur lors de la compilation.

int *q = static_cast<int*>(&c); // compile-time error

Réinterpréter le casting

Pour forcer la conversion du pointeur, de la même manière que le font le cast de style C en arrière-plan, le cast de réinterprétation serait utilisé à la place.

int *r = reinterpret_cast<int*>(&c); // forced conversion

Cette conversion 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 la configuration binaire sous-jacente.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 prudence s’il ne peut être complètement évité.

Casting dynamique

Celui-ci est uniquement utilisé pour convertir les pointeurs d’objet et les références d’objet en d’autres types de pointeurs ou de références dans la hiérarchie d’héritage.C'est le seul cast qui garantit que l'objet pointé peut être converti, en effectuant une vérification à l'exécution que le pointeur fait référence à un objet complet du type de destination.Pour que cette vérification à 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.En effet, le compilateur générera uniquement les informations de type d'exécution nécessaires pour ces objets.

Exemples de diffusion dynamique

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 Child 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 d'objet Enfant complet, cette conversion de pointeur échouera.Pour l'indiquer, la conversion dynamique renvoie un pointeur nul.Cela constitue un moyen pratique de vérifier si une conversion a réussi ou non pendant 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 conversion dynamique échouera alors 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
}

Casting dynamique ou statique

L'avantage d'utiliser une conversion dynamique est qu'elle permet au programmeur de vérifier si une conversion a réussi ou non pendant l'exécution.L’inconvénient est qu’il y a une surcharge de performances associée à cette vérification.Pour cette raison, l'utilisation d'un transtypage statique aurait été préférable dans le premier exemple, car une conversion dérivée en base n'échouera jamais.

MyBase *base = static_cast<MyBase*>(child); // ok

Cependant, dans le deuxième exemple, la conversion peut réussir ou échouer.Il échouera si l'objet MyBase contient une instance MyBase et réussira s'il contient une instance MyChild.Dans certaines situations, cela peut ne pas être connu avant l'exécution.Lorsque tel est le cas, la diffusion dynamique est un meilleur choix que la diffusion 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'une conversion statique au lieu d'une conversion 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);

Casting constant

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 invalide pouvant provoquer une erreur d'exécution.Cela pourrait se produire par exemple si la constante se trouvait dans une section de mémoire morte.

*nonConst = 10; // potential run-time error

Const cast est plutôt utilisé principalement lorsqu'il existe 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 recevoir une variable constante en utilisant un cast const.

print(&myConst); // error: cannot convert 
                 // const int* to int*

print(nonConst); // allowed

Source et plus d'explications

Tu devrais regarder l'article Programmation C++/Casting de types.

Il contient une bonne description de tous les différents types de distribution.Ce qui suit est tiré du lien ci-dessus :

const_cast

const_cast(expression) Le const_cast<>() est utilisé pour ajouter/supprimer constance (ou volatilité) d’une variable.

static_cast

static_cast(expression) L’static_cast<>() est utilisé pour effectuer un cast entre les types entiers.'Par exemple, char->long, int->short, etc.

La conversion statique est également utilisée pour convertir des pointeurs vers des types apparentés, par exemple Exemple de conversion de void* vers le type approprié.

dynamique_cast

La conversion dynamique est utilisée pour convertir les pointeurs et les références au moment de l’exécution, généralement dans le but de lancer un pointeur ou une référence vers le haut ou vers le bas une chaîne d’héritage (hiérarchie d’héritage).

dynamique_cast (expression)

Le type cible doit être un type de pointeur ou de référence, et l’attribut expression doit être évaluée à un pointeur ou à une référence.Travaux de moulage dynamique uniquement lorsque le type d’objet auquel l’expression fait référence est compatible avec le type cible et la classe de base a au moins un fonction de membre virtuel.Si ce n’est pas le cas, et le type d’expression en cours de conversion est un pointeur, NULL est retourné, si un cast dynamique sur une référence échoue, une exception bad_cast est levée.Lorsqu’il n’échoue pas, dynamique cast renvoie un pointeur ou une référence du type cible à l’objet à laquelle l’expression se réfère.

réinterpréter_cast

La réinterprétation du cast convertit simplement un type au niveau du bit en un autre.N’importe quel pointeur ou le type intégral peut être converti en n’importe quel autre avec réinterpréter le casting, permettant facilement une mauvaise utilisation.Par exemple, avec la réinterprétation de la première coulée, pourrait, de manière dangereuse, convertir un pointeur entier en un pointeur de chaîne.

Pour information, je crois que Bjarne Stroustrup aurait déclaré que les conversions de style C doivent être évitées et que vous devriez utiliser static_cast ou Dynamic_cast si possible.

FAQ sur le style C++ de Barne Stroustrup

Suivez ce conseil comme vous le souhaitez.Je suis loin d'être un gourou du C++.

Évitez d'utiliser des moulages de style C.

Les conversions de style C sont un mélange de conversions const et réinterprétées, et il est difficile de les trouver et de les remplacer dans votre code.Un programmeur d’applications C++ doit éviter le cast de style C.

Les conversions de style C confondent const_cast, static_cast et reinterpret_cast.

J'aurais aimé que C++ n'ait pas de conversions de style C.Les casts C++ se démarquent correctement (comme ils le devraient ;les conversions indiquent normalement que quelque chose de mal est fait) et distinguent correctement les différents types de conversion effectués par les conversions.Ils permettent également d'écrire des fonctions d'apparence similaire, par ex.boost::lexical_cast, ce qui est plutôt sympa du point de vue de la cohérence.

dynamic_cast a une vérification de type à l'exécution et ne fonctionne qu'avec des références et des pointeurs, alors que static_cast n'offre pas de vérification du type d'exécution.Pour des informations complètes, consultez l'article MSDN Opérateur static_cast.

dynamic_cast ne prend en charge que les types pointeur et référence.Il revient NULL si le cast est impossible si le type est un pointeur ou lève une exception si le type est un type référence.Ainsi, dynamic_cast peut être utilisé pour vérifier si un objet est d'un type donné, static_cast ne peut pas (vous vous retrouverez simplement avec une valeur invalide).

Les moulages de style C (et autres) ont été abordés dans les autres réponses.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top