1954 votes

Casting régulier vs. static_cast vs. dynamic_cast

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é les casts 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);

72 votes

Je n'appellerais pas l'ancien cast de style C un "cast normal" en C++, car c'est tout sauf cela. Vous ne devriez généralement pas l'utiliser en C++, surtout avec les classes, il est tout simplement trop facile de faire des erreurs avec. Son utilisation est le signe d'un programmeur C qui est passé au C++ mais qui n'a pas encore tout à fait appris le C++.

134 votes

Comment une question avec une réponse peut-elle être une duplication d'une question sans réponse ? de plus, cette question a été posée plus tôt que l'"originale".

7 votes

@Vladp Au cas où vous vous poseriez encore la question, ou que quelqu'un d'autre lise ceci et se demande . (De plus, pour mémoire, ce n'est pas un modérateur qui a fermé cette page, mais un utilisateur avec un mot de passe. marteau-pilon )

1780voto

Johannes Schaub - litb Points 256113

Static_cast

`static_cast` est utilisé pour les cas où vous voulez essentiellement inverser une conversion implicite, avec quelques restrictions et ajouts. `static_cast` n'effectue aucune vérification à l'exécution. Elle doit être utilisée si vous savez que vous vous référez à un objet d'un type spécifique, et donc qu'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 transmis un fichier MyClass et il n'est donc pas nécessaire d'effectuer une vérification au moment de l'exécution pour s'en assurer.

dynamic_cast

`dynamic_cast` est utile lorsque vous ne savez pas quel est le type dynamique de l'objet. Il retourne un pointeur nul si l'objet auquel il est fait référence ne contient pas le type casté en tant que classe de base (lorsque vous castez vers 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 (transfert vers une classe dérivée) et que le type de l'argument n'est pas polymorphe. Par exemple, le code suivant n'est pas valide, car Base ne contient pas de 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 valable avec les deux types de classes. static_cast y dynamic_cast et également sans aucun cast, car un "up-cast" est une conversion implicite (en supposant que la classe de base est accessible, c'est-à-dire qu'il s'agit d'une classe de type public l'héritage).

Casting régulier

Ces coulées sont également appelées coulées de style C. Un cast de style C est fondamentalement identique à l'essai d'une série de séquences de cast C++, et à l'adoption du premier cast C++ qui fonctionne, sans jamais considérer dynamic_cast . Inutile de dire qu'il est beaucoup plus puissant car il combine tous les éléments suivants const_cast , static_cast y reinterpret_cast mais il n'est pas sûr non plus, car il n'utilise pas la fonction dynamic_cast .

De plus, les castings de style C ne vous permettent pas seulement de faire cela, mais ils vous permettent également de casters en toute sécurité vers une classe de base privée, alors que l'"équivalent" static_cast La séquence vous donnerait une erreur de compilation pour cela.

Certaines personnes préfèrent les casts de style C en raison de leur brièveté. Je les utilise uniquement pour les casts numériques, et j'utilise les casts C++ appropriés lorsque des types définis par l'utilisateur sont impliqués, car ils fournissent une vérification plus stricte.

14 votes

Voir aussi les deux castings supplémentaires de Boost : boost.org/doc/libs/1_47_0/libs/conversion/

3 votes

@JohannesSchaub-litb : Êtes-vous sûr qu'un cast de style C vous permet de faire un cast "sûr" vers une classe de base privée ? Je peux imaginer que cela fonctionne lorsque la classe de base privée est la seule /base/, mais qu'en est-il de l'héritage virtuel/multiple ? Je suppose que le cast de style C ne fait aucune manipulation de pointeur.

2 votes

@JohannesSchaub-litb est-il vrai qu'il y a également des frais généraux liés à l'utilisation des anciens casts de style c par rapport aux casts C++ ?

258voto

Hossein Points 1789

Moulage statique

Le static cast effectue des conversions entre types compatibles. Il est similaire au cast de style C, mais est plus restrictif. Par exemple, le cast de style C permettrait à un pointeur d'entier de pointer vers un char.

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

Comme il en résulte un pointeur de 4 octets pointant sur 1 octet de mémoire allouée, écrire sur ce pointeur provoquera une erreur d'exécution ou écrasera de la mémoire adjacente.

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

Contrairement au cast de style C, le cast statique permet au compilateur de vérifier que les types de données du pointeur et du pointeur sont compatibles, ce qui permet au programmeur de détecter cette affectation incorrecte du pointeur pendant 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 cast de style C le fait en arrière-plan, le cast reinterpret serait utilisé à la place.

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

Ce cast gère les conversions entre certains types non liés, par exemple d'un type de pointeur vers un autre type de pointeur incompatible. Elle effectue simplement une copie binaire des données sans modifier le modèle binaire 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. Elle doit être utilisée avec prudence si elle ne peut être évitée.

Distribution dynamique

Celui-ci est uniquement utilisé pour convertir les pointeurs d'objets et les références d'objets 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. C'est-à-dire que la classe doit définir ou hériter d'au moins une fonction virtuelle. En effet, le compilateur ne génère les informations de type nécessaires à l'exécution que pour de tels objets.

Exemples de distribution dynamique

Dans l'exemple ci-dessous, un MyChild est converti en un pointeur MyBase en utilisant une conversion dynamique. Cette conversion dérivée-base réussit, car l'objet Child comprend 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 MyBase pointeur vers un MyChild pointeur. Comme l'objet Base ne contient pas d'objet Child complet, cette conversion de pointeur échouera. Pour l'indiquer, le cast dynamique renvoie un pointeur nul. Cela donne 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, le cast dynamique échouera alors en lançant un bad_cast exception. Celle-ci doit être traitée à l'aide d'une try-catch déclaration.

#include <exception>
// …  
try
{ 
  MyChild &child = dynamic_cast<MyChild&>(*base);
}
catch(std::bad_cast &e) 
{ 
  std::cout << e.what(); // bad dynamic_cast
}

Distribution dynamique ou statique

L'avantage d'utiliser un cast dynamique est qu'il 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 performance associée à cette vérification. Pour cette raison, l'utilisation d'un cast statique aurait été préférable dans le premier exemple, car une conversion dérivé-base n'échouera jamais.

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

Cependant, dans le deuxième exemple, la conversion peut soit réussir, soit échouer. Elle échouera si le MyBase contient un MyBase et elle réussira si elle contient une MyChild exemple. Dans certaines situations, cela peut ne pas être connu avant l'exécution. Lorsque c'est le cas, le cast dynamique est un meilleur choix que le cast statique.

// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);

Si la conversion base/dérivée avait été effectuée en utilisant un cast statique au lieu d'un cast dynamique, la conversion n'aurait pas échoué. Elle 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);

Const cast

Celui-ci est principalement utilisé pour ajouter ou supprimer l'élément const modificateur d'une variable.

const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const

Bien que const cast permet de modifier la valeur d'une constante, cela reste du code invalide qui peut provoquer une erreur d'exécution. Cela peut se produire, par exemple, si la constante est située dans une section de la mémoire en lecture seule.

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

const En revanche, le cast est utilisé principalement lorsqu'il existe une fonction qui prend un argument pointeur non constant, même si elle ne modifie pas le pointeur.

void print(int *p) 
{
   std::cout << *p;
}

On peut ensuite passer à la fonction une variable constante en utilisant une fonction const la distribution.

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

print(nonConst); // allowed

Source et autres explications

0 votes

std::bad_cast est défini dans <typeinfo>

1 votes

De l'enfant à la base, le moulage n'est pas nécessaire : MyBase *base = child; // ok

5 votes

A mon avis, la meilleure réponse, très simple et pourtant claire

162voto

Mats Fredriksson Points 7136

Static Cast

static_cast n'effectue aucune vérification des types concernés au moment de l'exécution, ce qui signifie qu'à moins de savoir ce que vous faites, ils peuvent être très dangereux. Il ne permet également le casting qu'entre des types apparentés, tels que des pointeurs ou des références entre Base et Derived, ou entre des types fondamentaux, tels que long vers int ou int vers float.

Il ne permet pas les castings entre des types fondamentalement différents, comme un cast entre une BaseA et une BaseB si elles ne sont pas liées. Cela entraînera une erreur de compilation.

Casting dynamique

dynamic_cast effectue également une vérification au moment de l'exécution, et si l'instance ne peut pas être convertie en un autre type dérivé, elle renvoie un pointeur nul.

Note : le dynamic cast ne fonctionne que si le type source est polymorphe, sinon le compilateur donnera une erreur telle que * error : cannot dynamic_cast 'b' (of type 'class base*') to type 'class inh1*' (source type is not polymorphic)*.

Exemples

Si nous avons les classes suivantes

class B {};

class D : B {};

vous pouvez alors faire ce qui suit

B* b = new D();
D* d1 = static_cast<D*>b; // Valid! d1 is a valid and correct pointer to a D
D* d2 = dynamic_cast<D*>b; // Valid! d2 is a valid and correct pointer to a D

Dans cet exemple, les deux pointeurs d1 et d2 pointeront vers une version typée correcte de b

Le problème se pose dans l'exemple suivant :

B* b = new B();
D* d1 = static_cast<D*>b; // Invalid!
D* d2 = dynamic_cast<D*>b; // Valid, but d2 is now a null pointer

Maintenant, d1 pointera vers un segment de données de type D*, mais les données réelles sont B*, ce qui entraînera des problèmes de mémoire et de corruption. d2, en revanche, sera un pointeur nul et pourra être vérifié et traité correctement.

Comme dynamic_cast effectue une vérification des types au moment de l'exécution, elle est également plus lente.

EDITAR:

Étant donné que dynamic_cast peut entraîner un surcroît de temps d'exécution, il est possible de le désactiver en demandant au compilateur de ne pas inclure les informations sur le type d'exécution.

Il existe également d'autres opérateurs de distribution.

Réinterpréter le casting

Il s'agit du cast ultime, qui ignore toute forme de sécurité de type, vous permettant de cast n'importe quoi vers n'importe quoi d'autre, en réaffectant fondamentalement les informations de type du motif binaire.

int i = 12345;
MyClass* p = reinterpret_cast<MyClass*> i;

C'est très dangereux à moins que vous ne sachiez ce que vous faites, et c'est essentiellement l'équivalent du C-cast. Comme ça ;

int i = 0;
void *v = 0;
int c = (int)v; // is valid
int d = static_cast<int>(v); // is not valid, different types
int e = reinterpret_cast<int>(v); // is valid, but very dangerous

Et puis nous avons le const_cast<> qui supprime le caractère constant d'une variable.

90voto

Thomas Points 6891

Vous devriez consulter l'article _Programmation C++/Casting de type_ .

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

const_cast

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

static_cast

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

Le cast statique est également utilisé pour caster des pointeurs vers des types apparentés, par exemple par exemple, en transférant void* vers le type approprié.

dynamic_cast

Le cast dynamique est utilisé pour convertir les pointeurs et les références au moment de l'exécution, généralement dans le but de transférer 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 cible doit être un pointeur ou un type de référence, et l'expression doit être évaluée comme un pointeur ou une référence. Le cast dynamique fonctionne fonctionne uniquement lorsque le type d'objet auquel l'expression fait référence est compatible avec le type cible et la classe de base possède au moins une fonction membre fonction membre virtuelle. Si ce n'est pas le cas, et que le type de l'expression qui est castée 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, le dynamique renvoie un pointeur ou une référence du type cible à l'objet auquel l'expression auquel l'expression fait référence.

réinterpréter_cast

Reinterpret cast permet simplement de convertir un type en un autre, bit à bit. Tout pointeur ou intégral peut être converti en n'importe quel autre type avec reinterpret cast, ce qui permet facilement une mauvaise utilisation. Par exemple, avec reinterpret cast, un pourrait, de manière non sûre, convertir un pointeur entier en un pointeur de chaîne de caractères.

33voto

Jason Baker Points 56682

Pour information, je crois que Bjarne Stroustrup aurait dit que les casts de style C sont à éviter et que vous devriez utiliser static_cast ou dynamic_cast si possible.

FAQ sur le style C++ de Barne Stroustrup

Prenez ce conseil pour ce que vous voulez. Je suis loin d'être un gourou du C++.

14 votes

^ Ouais, parce que les castings C++ qui sont explicitement étiquetés et délibérément confinés à des rôles bien définis sont plus "infernaux" qu'un cast C, qui essaie juste aveuglément de multiples types de castings jusqu'à ce que tout ce qui est fonctionne, quel que soit le sens... bien joué.

0 votes

En fait, si vous lisez sa FAQ, Stroustrup vous recommande d'éviter les plâtres. La première phrase de sa section sur static_cast : "Il est généralement préférable d'éviter les casts".

0 votes

@BillWeinman dans la pratique, on ne peut pas éviter complètement les casts (et en ce qui me concerne, la formulation "mieux éviter" le permet). Dès que vous interfacez votre propre code avec des API ou différentes API entre elles, vous vous retrouvez le plus souvent dans des situations où les types ne correspondent pas exactement et où vous devez avoir recours au casting. C'est particulièrement vrai pour les API plus anciennes et qui se sont développées organiquement. WinAPI en est un excellent exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X