c++ - style - Cast standard vs static_cast vs dynamic_cast




static_cast vs c style cast (6)

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

Source et plus d'explications


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.





casting