Question Cast standard vs static_cast vs dynamic_cast [en double]


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, 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);

1468
2017-08-26 13:20


origine


Réponses:


static_cast

static_cast est utilisé pour les cas où vous voulez inverser une conversion implicite, avec quelques restrictions et additions. static_cast n'effectue aucune vérification à l'exécution. Cela devrait être utilisé si vous savez que vous faites référence à 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 MyClass objet, et donc il n'y a pas besoin d'un contrôle d'exécution pour assurer cela.

dynamic_cast

dynamic_cast est utile lorsque vous ne savez pas quel est 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, un bad_cast 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 êtes 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 les deux static_cast et dynamic_cast, et aussi sans aucun casting, 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 prendre en compte dynamic_cast. Inutile de dire que c'est beaucoup plus puissant car il combine tous const_cast, static_cast et reinterpret_cast, mais c'est aussi dangereux, car il n'utilise pas dynamic_cast.

De plus, les transtypes de style C vous permettent non seulement de faire cela, 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 distributions C ++ appropriées lorsque des types définis par l'utilisateur sont impliqués, car ils permettent une vérification plus stricte.


1413
2017-08-10 13:50



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 à la place d'un pointeur, la distribution dynamique échoue en émettant 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


116
2017-08-26 13:28



Vous devriez regarder l'article Programmation C ++ / Coulée de type.

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

const_cast

const_cast (expression) Le const_cast <> () est utilisé pour ajouter / supprimer   const (ness) (ou volatile-ness) d'une variable.

static_cast

static_cast (expression) static_cast <> () est utilisé pour lancer entre   les types entiers. 'par exemple.' char-> long, int-> court etc.

La distribution statique est également utilisée pour diffuser des pointeurs vers des types apparentés, par exemple   exemple de coulée void * au type approprié.

dynamic_cast

La distribution dynamique est utilisée pour convertir des pointeurs et des références lors 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).

dynamic_cast (expression)

Le type de cible doit être un pointeur ou un type de référence, et le   l'expression doit correspondre à un pointeur ou une référence. Travaux de coulée dynamique   seulement lorsque le type d'objet auquel se réfère l'expression est   compatible avec le type de cible et la classe de base a au moins un   fonction de membre virtuel. Sinon, et le type d'expression en cours   est un pointeur, NULL est renvoyé, si un cast dynamique sur une référence   échoue, une exception bad_cast est levée. Quand ça n'échoue pas, dynamique   cast renvoie un pointeur ou une référence du type cible à l'objet   à quelle expression fait référence.

réinterpréter_cast

Réinterpréter le cast jette simplement un type bitwise à l'autre. Tout pointeur   ou le type intégral peut être casté à n'importe quel autre avec réinterpréter la fonte,   facilement permettre une mauvaise utilisation. Par exemple, avec réinterpréter jeter un   peut, de façon non sûre, convertir un pointeur entier en un pointeur de chaîne.


71
2017-09-19 17:30



Évitez d'utiliser des moules de style C.

Les transtypages de type C sont un mélange de cast const et reinterpret, et il est difficile de trouver et remplacer dans votre code. Un programmeur d'application C ++ devrait éviter la distribution de type C.


23
2017-08-26 13:39



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 ++.


21
2017-08-26 13:38



Les casts de type C regroupent const_cast, static_cast et reinterpret_cast.

Je souhaite que C ++ n'ait pas de conversions de type 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 ex. boost :: lexical_cast, ce qui est plutôt sympa du point de vue de la cohérence.


10
2017-08-26 13:26



dynamic_casta la vérification de 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 Opérateur.


8
2018-02-05 17:10



dynamic_cast prend uniquement en charge les types de pointeur et de référence. Ça revient NULL si la conversion 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 allez simplement vous retrouver avec une valeur invalide).

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


7