Menu

Conversion de types en C++ : static_cast, conversions implicites et casts

Comment fonctionne la conversion de types en C++ : conversions implicites, le piège de la division entière et les quatre casts nommés (static_cast, const_cast, reinterpret_cast, dynamic_cast), avec les subtilités qui provoquent une perte de données silencieuse.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Ce qu'est la conversion de types

Convertir des types signifie transformer une valeur d'un type vers un autre : transformer un double en int, un char en son code numérique, ou un pointeur de classe de base en pointeur de classe dérivée. Le C++ effectue certaines de ces conversions pour toi automatiquement, mais celles qu'il fait en silence sont précisément là où se cachent les bugs.

Il y a deux variantes : les conversions implicites qui se produisent toutes seules, et les casts explicites que tu écris toi-même. Tu as déjà rencontré un symptôme de cela dans opérateurs : la division entière. Le cast est la façon d'en reprendre le contrôle.

Conversions implicites

Quand tu mélanges des types numériques dans une expression, le C++ promeut le type « le plus petit » vers « le plus grand » pour que les deux côtés correspondent. Cela fait en général ce que tu veux.

Les ennuis commencent quand la conversion va dans l'autre sens : d'un type plus large vers un type plus étroit. C'est une conversion par restriction, et elle peut perdre des données en silence.

De float vers int, on tronque vers zéro : il n'y a pas d'arrondi, donc 3.99 devient 3. Et faire entrer 300 dans un char provoque un débordement. Beaucoup de compilateurs avertissent ici ; certains non. Quand tu veux vraiment restreindre, dis-le explicitement avec un cast pour que le prochain lecteur sache que c'était intentionnel.

Corriger le piège de la division entière

La raison la plus fréquente de faire un cast est la division. Quand les deux opérandes sont des entiers, / effectue une division entière et jette le reste.

La correction consiste à appliquer static_cast<double> à un opérande avant la division. Une erreur fréquente est static_cast<double>(got / total) : c'est trop tard, car got / total vaut déjà 0 au moment où le cast s'exécute, donc tu obtiens 0.0. Convertis un opérande, pas le résultat.

static_cast : ton cast par défaut

Le C++ te donne quatre casts nommés. Celui que tu utiliseras 95 % du temps est static_cast<T>(value), qui réalise des conversions bien définies entre types apparentés : conversions numériques, d'enum vers int, de void* de retour vers un pointeur typé, et la montée/descente dans une hiérarchie de classes quand tu connais déjà le type.

Préfère static_cast à l'ancien cast à la C (int)balance. Un cast à la C essaiera n'importe quelle conversion pour que le code compile, y compris les dangereuses ci-dessous, si bien qu'il peut retirer le const en silence ou réinterpréter des octets bruts. static_cast n'autorise que les conversions que le compilateur peut réellement justifier, et le verbeux static_cast<...> est trivial à retrouver lors d'une revue de code.

// À éviter - cast à la C, aucun filet de sécurité :
int dollars = (int) balance;

// À préférer - explicite, vérifié, facile à retrouver :
int dollars = static_cast<int>(balance);

Les trois autres casts (à utiliser avec parcimonie)

Les casts restants existent pour des tâches précises et étroites. N'y recours que lorsque static_cast ne peut vraiment pas le faire.

const_cast retire (ou ajoute) const. Son seul usage légitime est d'appeler une API à la C qui a oublié de marquer un paramètre const. Modifier, via un const_cast, un objet qui a été initialement déclaré const est un comportement indéfini.

void legacyApi(char* msg);   // ancienne API, ne prend pas de const

const char* text = "hello";
legacyApi(const_cast<char*>(text));   // correct uniquement si legacyApi n'y écrit pas

reinterpret_cast réinterprète le motif de bits brut, par exemple un pointeur comme une adresse entière. Il n'effectue aucune conversion et est extrêmement dangereux ; c'est presque toujours le signe que tu devrais repenser la conception.

dynamic_cast convertit en toute sécurité un pointeur ou une référence de classe de base vers un type dérivé à l'exécution, en utilisant le type réel de l'objet. Il exige une base polymorphe (une classe avec au moins une fonction virtual) et renvoie nullptr si le cast ne s'applique pas.

Si a avait pointé vers un autre Animal, dynamic_cast<Dog*> renverrait nullptr et la branche else s'exécuterait, ce qui est exactement la raison pour laquelle il est plus sûr que d'utiliser aveuglément static_cast pour descendre dans une hiérarchie.

Erreurs courantes à éviter

  • Convertir le résultat au lieu d'un opérande. static_cast<double>(a / b) supprime d'abord ta fraction. Convertis a ou b.
  • Supposer que de float vers int on arrondit. On tronque : static_cast<int>(2.99) vaut 2. Pour arrondir, utilise std::round, std::lround, etc.
  • Recourir à un cast à la C. Il masque quelle conversion a lieu. Utilise static_cast et tu obtiendras une erreur de compilation quand la conversion est dangereuse, plutôt qu'une surprise silencieuse.
  • Restreindre vers un type trop petit. Convertir 300 en char ou un énorme long en int provoque un enveloppement ou un débordement. Choisis un type cible assez large pour la plage.

Suivant : If-Else

Maintenant que tu peux convertir et comparer des valeurs proprement, l'étape suivante est de prendre des décisions avec elles. L'instruction if-else exécute du code différent selon qu'une condition est true : le fondement de tout programme à branchements.

Questions fréquentes

Quelle est la différence entre static_cast et un cast à la C en C++ ?

Un cast à la C comme (int)x essaie chaque conversion à tour de rôle : il peut devenir en silence un dangereux reinterpret_cast ou retirer le const. static_cast<int>(x) ne réalise que des conversions apparentées que le compilateur peut vérifier, si bien que le compilateur rejette les absurdités. En C++ moderne, préfère toujours static_cast aux casts à la C ; c'est plus sûr et bien plus facile à retrouver avec grep.

Comment convertir un int en double en C++ ?

Utilise static_cast<double>(x). Cela compte surtout pour la division : 5 / 2 est une division entière et donne 2, mais static_cast<double>(5) / 2 donne 2.5. Convertis l'un des opérandes avant que la division n'ait lieu : convertir le résultat, static_cast<double>(5 / 2), arrive trop tard et donne quand même 2.0.

Pourquoi convertir une grande valeur vers un type plus petit donne-t-il un nombre erroné en C++ ?

Convertir vers un type qui ne peut pas contenir la valeur est une conversion par restriction. De float vers int, la partie fractionnaire est tronquée (static_cast<int>(3.99) vaut 3), et un entier hors limites est soit ramené par enveloppement (non signé), soit défini par l'implémentation (signé). Le compilateur ne t'arrête généralement pas, alors convertis de manière délibérée et assure-toi que le type cible est assez large.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER