Module 06 : Casts C++
Concepts Clés :
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
- Identification de type
- Conversions définies par l’utilisateur
- Le mot-clé explicit
Pourquoi C’est Important
Section intitulée « Pourquoi C’est Important »Le C++ fournit quatre opérateurs de cast explicites pour remplacer les casts de style C. Chacun a un objectif spécifique, rendant vos intentions claires et détectant les erreurs à la compilation.
Utilisez le bon cast pour le travail—cela documente votre intention et aide à prévenir les bugs.
Comprendre les Hiérarchies de Types
Section intitulée « Comprendre les Hiérarchies de Types »Avant de plonger dans les casts, comprenez que les types forment des hiérarchies basées sur la précision et la généralité :
Plus Spécifique (étroit) Plus Général (large) int ────────> double Derived* ────────> Base* const int* ────────> int* (retire la restriction) int* ────────> void*Idée clé : Les conversions de spécifique vers général sont généralement sûres et implicites. Les conversions de général vers spécifique nécessitent des casts explicites car des informations peuvent être perdues.
int i = 42;double d = i; // Implicite : int -> double (sûr, pas de perte)int j = d; // Attention ! double -> int perd la partie décimale
Derived* dp = new Derived();Base* bp = dp; // Implicite : Derived* -> Base* (sûr, relation EST-UN)Derived* dp2 = bp; // Erreur ! Base* -> Derived* nécessite un cast expliciteConversion vs Réinterprétation
Section intitulée « Conversion vs Réinterprétation »Deux opérations fondamentalement différentes :
- Conversion : Transforme la valeur (ex:
3.14devient3) - Réinterprétation : Garde les mêmes bits, interprète différemment (ex: traite un pointeur comme un entier)
Les casts C++ distinguent ces opérations ; les casts de style C ne le font pas.
Comprendre les Opérateurs de Cast C++
Section intitulée « Comprendre les Opérateurs de Cast C++ »Ce module introduit les casts de style C++. Chaque cast a un objectif spécifique et est plus sûr que les casts de style C :
L’Opérateur static_cast<>
Section intitulée « L’Opérateur static_cast<> »int i = static_cast<int>(3.14); // Convertit double en intBase* bp = static_cast<Base*>(derived); // Upcast (sûr)Derived* dp = static_cast<Derived*>(bp); // Downcast (non vérifié !)static_cast<>effectue des conversions de type à la compilation- Il vérifie à la compilation que la conversion a du sens (principalement)
- Sûr pour : conversions numériques, upcasts, conversions void*
- Dangereux pour : downcasts (pas de vérification à l’exécution - suppose que vous savez que c’est sûr)
- C’est “static” car il vérifie les types à la compilation (vérification de type statique)
L’Opérateur dynamic_cast<>
Section intitulée « L’Opérateur dynamic_cast<> »Derived* dp = dynamic_cast<Derived*>(basePtr);if (dp != NULL) { // Cast réussi - basePtr pointe réellement vers Derived !}dynamic_cast<>effectue des conversions de type à l’exécution- Il vérifie à l’exécution si le cast est réellement valide
- Retourne
NULLpour les pointeurs si le cast échoue - Lance une exception
std::bad_castpour les références si le cast échoue - Fonctionne uniquement avec les classes polymorphes (doit avoir au moins une fonction virtuelle)
- C’est “dynamic” car il vérifie les types à l’exécution (vérification de type dynamique)
L’Opérateur const_cast<>
Section intitulée « L’Opérateur const_cast<> »int* p = const_cast<int*>(const_ptr); // Retire constconst int* cp = const_cast<const int*>(p); // Ajoute constconst_cast<>change uniquement les qualificateurs const/volatile- Il ne peut pas changer le type sous-jacent (int reste int)
- Retirer const de données vraiment const mène à un comportement indéfini !
- Sûr à utiliser uniquement quand les données originales n’étaient pas vraiment const
- Principalement utilisé pour la compatibilité avec des APIs legacy qui ne sont pas const-correct
L’Opérateur reinterpret_cast<>
Section intitulée « L’Opérateur reinterpret_cast<> »uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);char* bytes = reinterpret_cast<char*>(&data);reinterpret_cast<>effectue une réinterprétation de bits de bas niveau- Il dit au compilateur “fais-moi confiance, traite cette mémoire comme un type différent”
- Cast le plus dangereux - il ne vérifie rien, réinterprète juste les bits
- Utilisez pour : pointeur-vers-entier, entier-vers-pointeur, types de pointeurs non reliés
- Les résultats sont dépendants de la plateforme et peuvent violer la sécurité des types
- N’utilisez que quand vous SAVEZ VRAIMENT ce que vous faites
Le Cast de Style C () (À ÉVITER !)
Section intitulée « Le Cast de Style C () (À ÉVITER !) »int x = (int)3.14; // Style C - fait N'IMPORTE quel type de cast- Les casts de style C peuvent tout faire :
static_cast,const_cast,reinterpret_cast - Trop puissant - difficile de voir ce qui se passe réellement
- Non recherchable - impossible de grep tous les casts facilement
- Exigence du Module 06 : les conversions doivent utiliser un operateur de cast specifique (les casts de style C cachent lequel vous utilisez)
1. Pourquoi les Casts C++ ?
Section intitulée « 1. Pourquoi les Casts C++ ? »Problèmes du Cast de Style C
Section intitulée « Problèmes du Cast de Style C »// Les casts de style C font tout - trop puissants, pas de clartéint x = (int)3.14; // OKint* p = (int*)&x; // Dangereuxconst int* cp = &x;int* mp = (int*)cp; // Retire const - dangereux !Avantages des Casts C++
Section intitulée « Avantages des Casts C++ »- Intention explicite : Clair quel type de conversion
- Recherchable : Facile de grep les casts
- Type-safe : Vérifications à la compilation/exécution
- Restrictif : Chaque cast ne fait que des conversions spécifiques
2. static_cast
Section intitulée « 2. static_cast »Objectif
Section intitulée « Objectif »Conversions vérifiées à la compilation entre types reliés.
Cas d’Utilisation
Section intitulée « Cas d’Utilisation »1. Conversions numériques
double d = 3.14;int i = static_cast<int>(d); // 3float f = static_cast<float>(i); // 3.0f2. Enum vers int (et vice versa)
enum Color { RED, GREEN, BLUE };Color c = static_cast<Color>(1); // GREENint n = static_cast<int>(c); // 13. Up/downcasts de pointeurs (sans polymorphisme)
class Base { };class Derived : public Base { };
Derived d;Base* bp = static_cast<Base*>(&d); // Upcast (sûr)Derived* dp = static_cast<Derived*>(bp); // Downcast (non vérifié !)4. Conversions void*
int x = 42;void* vp = static_cast<void*>(&x);int* ip = static_cast<int*>(vp);Quand NE PAS Utiliser
Section intitulée « Quand NE PAS Utiliser »// Types non reliés - ne compile pasdouble* dp;int* ip = static_cast<int*>(dp); // ERREUR
// Suppression de const - utilisez const_castconst int* cp;int* p = static_cast<int*>(cp); // ERREUR3. dynamic_cast
Section intitulée « 3. dynamic_cast »Objectif
Section intitulée « Objectif »Downcasts vérifiés à l’exécution dans les hiérarchies de classes polymorphes.
Prérequis
Section intitulée « Prérequis »- La classe de base doit avoir au moins une fonction virtuelle
- Fonctionne avec les pointeurs et les références
Pourquoi les Fonctions Virtuelles Sont Requises (RTTI)
Section intitulée « Pourquoi les Fonctions Virtuelles Sont Requises (RTTI) »dynamic_cast utilise RTTI (Runtime Type Information) pour vérifier les types à l’exécution. RTTI n’est stocké que pour les classes polymorphes (classes avec fonctions virtuelles).
Quand une classe a des fonctions virtuelles, le compilateur ajoute un pointeur caché (vptr) à chaque objet qui pointe vers une vtable. La vtable contient non seulement des pointeurs de fonction mais aussi des informations de type que dynamic_cast utilise.
class NotPolymorphic { }; // Pas de RTTIclass Polymorphic { virtual ~Polymorphic() {} }; // A RTTI
// Ceci échoue à la compilation - pas de RTTI disponible :// NotPolymorphic* np = dynamic_cast<NotPolymorphic*>(ptr);
// Ceci fonctionne - RTTI disponible :Polymorphic* pp = dynamic_cast<Polymorphic*>(ptr);Syntaxe Pointeur
Section intitulée « Syntaxe Pointeur »class Base {public: virtual ~Base() {}};class Derived : public Base { };
Base* bp = new Derived();Derived* dp = dynamic_cast<Derived*>(bp);
if (dp != NULL) { // Cast réussi - bp pointait réellement vers Derived}else { // Cast échoué - bp pointait vers Base ou autre type}Syntaxe Référence
Section intitulée « Syntaxe Référence »Base& br = derived_object;try { Derived& dr = dynamic_cast<Derived&>(br); // Cast réussi}catch (std::bad_cast& e) { // Cast échoué}Pourquoi la Vérification à l’Exécution ?
Section intitulée « Pourquoi la Vérification à l’Exécution ? »class Animal { public: virtual ~Animal() {} };class Dog : public Animal { };class Cat : public Animal { };
Animal* animal = getAnimalFromUser(); // Pourrait être Dog ou Cat
Dog* dog = dynamic_cast<Dog*>(animal);if (dog) { dog->bark(); // Sûr - nous savons que c'est vraiment un Dog}4. const_cast
Section intitulée « 4. const_cast »Objectif
Section intitulée « Objectif »Ajouter ou retirer les qualificateurs const/volatile.
Cas d’Utilisation
Section intitulée « Cas d’Utilisation »Retirer const (utilisez avec précaution !)
void legacyFunction(char* str); // Impossible de changer cette vieille fonction
const char* message = "Hello";legacyFunction(const_cast<char*>(message)); // Retire const
// ATTENTION : Modifier des données vraiment const est un COMPORTEMENT INDÉFINI !Ajouter const
int x = 42;const int* cp = const_cast<const int*>(&x);Quand C’est Sûr
Section intitulée « Quand C’est Sûr »// L'original était non-const, temporairement rendu constint x = 42;const int* cp = &x;int* p = const_cast<int*>(cp); // Sûr - x n'a jamais été const*p = 100; // OKQuand C’est DANGEREUX
Section intitulée « Quand C’est DANGEREUX »const int CONSTANT = 42;int* p = const_cast<int*>(&CONSTANT);*p = 100; // COMPORTEMENT INDÉFINI ! CONSTANT est vraiment const5. reinterpret_cast
Section intitulée « 5. reinterpret_cast »Objectif
Section intitulée « Objectif »Réinterprétation de bas niveau des patterns de bits. Cast le plus dangereux.
Cas d’Utilisation
Section intitulée « Cas d’Utilisation »Pointeur vers entier (et retour)
int x = 42;uintptr_t address = reinterpret_cast<uintptr_t>(&x);int* p = reinterpret_cast<int*>(address);Pointeur vers type différent
struct Data { int x; float y; };Data d = {42, 3.14f};char* bytes = reinterpret_cast<char*>(&d);// Maintenant peut accéder aux octets bruts de dAvertissements
Section intitulée « Avertissements »- Hautement dépendant de la plateforme
- Peut violer les règles d’aliasing
- N’utilisez que quand vous SAVEZ VRAIMENT ce que vous faites
6. Opérateurs de Conversion Définis par l’Utilisateur
Section intitulée « 6. Opérateurs de Conversion Définis par l’Utilisateur »Les classes peuvent définir comment elles se convertissent VERS d’autres types en utilisant des opérateurs de conversion.
class Fraction {private: int _numerator; int _denominator;
public: Fraction(int num, int den) : _numerator(num), _denominator(den) {}
// Opérateur de conversion : Fraction -> double operator double() const { return static_cast<double>(_numerator) / _denominator; }
// Opérateur de conversion : Fraction -> bool (est-ce non-zéro ?) operator bool() const { return _numerator != 0; }};
// UtilisationFraction half(1, 2);double d = half; // Appelle operator double(), d = 0.5if (half) { // Appelle operator bool() std::cout << "Fraction non-zéro" << std::endl;}Quand Utiliser
Section intitulée « Quand Utiliser »- Convertir votre classe vers des types primitifs
- Permettre votre classe dans des contextes booléens (
if,while) - Interopérabilité avec des APIs attendant des types différents
7. Le Mot-Clé explicit
Section intitulée « 7. Le Mot-Clé explicit »Les constructeurs peuvent agir comme des opérateurs de conversion implicites. Le mot-clé explicit empêche cela.
Le Problème : Conversion Implicite par Constructeur
Section intitulée « Le Problème : Conversion Implicite par Constructeur »class String {public: String(int size) { // Alloue une string de taille donnée _data = new char[size]; _size = size; } // ...};
void printString(const String& s);
// Conversion implicite accidentelle !printString(42); // Crée String(42) - probablement pas ce que vous vouliez !La Solution : Mot-Clé explicit
Section intitulée « La Solution : Mot-Clé explicit »class String {public: explicit String(int size) { // Marque le constructeur explicit _data = new char[size]; _size = size; }};
printString(42); // Erreur ! Pas de conversion impliciteprintString(String(42)); // OK - construction expliciteQuand Utiliser explicit
Section intitulée « Quand Utiliser explicit »Utilisez explicit sur les constructeurs à un argument sauf si vous voulez spécifiquement la conversion implicite :
class Vector3 {public: explicit Vector3(float all); // explicit : Vector3(5.0f) pas depuis un simple 5.0f Vector3(float x, float y, float z); // Arguments multiples - ne peut pas être implicite de toute façon};
class Wrapper {public: Wrapper(const std::string& s); // Peut-être que l'implicite est OK ici explicit Wrapper(int fd); // Mais pas depuis un simple int};8. Spectre de Sécurité des Casts
Section intitulée « 8. Spectre de Sécurité des Casts »Les casts C++ varient dans ce qu’ils vérifient :
| Cast | Niveau de Vérification | Sécurité |
|---|---|---|
dynamic_cast | Exécution | Le plus sûr |
static_cast | Compilation | Modéré |
const_cast | Compilation | Dépend du contexte |
reinterpret_cast | Aucun | Le plus dangereux |
Choisissez le cast le moins permissif qui fonctionne pour votre situation.
9. Exercices du Module 06
Section intitulée « 9. Exercices du Module 06 »ex00 : Convertisseur de Types Scalaires
Section intitulée « ex00 : Convertisseur de Types Scalaires »class ScalarConverter {private: ScalarConverter(); // Non instanciable
public: static void convert(const std::string& literal);};
// Détecte le type et convertit vers tous les types scalaires// Entrée : "42", "42.0f", "42.0", "'*'", "nan", etc.// Sortie : représentations char, int, float, double
void ScalarConverter::convert(const std::string& literal) { // 1. Détecter le type d'entrée (char, int, float, double) // 2. Parser la valeur // 3. Convertir et afficher les quatre types
// Gérer les cas spéciaux : nan, inf, conversions impossibles}Logique de Détection de Type :
bool isChar(const std::string& s) { return s.length() == 1 && !isdigit(s[0]);}
bool isInt(const std::string& s) { // Vérifier si tous les caractères sont des chiffres (avec signe optionnel)}
bool isFloat(const std::string& s) { // Vérifier le suffixe 'f', le point décimal // Gérer "nanf", "-inff", "+inff"}
bool isDouble(const std::string& s) { // A un point décimal, pas de suffixe 'f' // Gérer "nan", "-inf", "+inf"}Conversion String vers Nombre : strtod
Section intitulée « Conversion String vers Nombre : strtod »#include <cstdlib> // pour strtod, strtol
// strtod - string vers double (plus sûr que atof)const char* str = "3.14159";char* endptr;double value = std::strtod(str, &endptr);
// Vérifier les erreurs de conversionif (endptr == str) { // Aucune conversion effectuée std::cout << "Nombre invalide" << std::endl;}else if (*endptr != '\0') { // Conversion partielle (caractères restants) std::cout << "Caractères restants : " << endptr << std::endl;}
// Aussi utile : strtol pour les entierslong intValue = std::strtol(str, &endptr, 10); // base 10Détection de Valeurs Spéciales : isnan, isinf
Section intitulée « Détection de Valeurs Spéciales : isnan, isinf »#include <cmath> // pour isnan, isinf
double value = std::strtod(str, NULL);
// Vérifier NaN (Not a Number)if (std::isnan(value)) { std::cout << "La valeur est nan" << std::endl;}
// Vérifier l'infiniif (std::isinf(value)) { if (value > 0) std::cout << "La valeur est +inf" << std::endl; else std::cout << "La valeur est -inf" << std::endl;}Classification de Caractères : isprint, isdigit
Section intitulée « Classification de Caractères : isprint, isdigit »#include <cctype>
char c = '*';
// isprint - est-ce un caractère imprimable ?if (std::isprint(c)) { std::cout << "char: '" << c << "'" << std::endl;}else { std::cout << "char: Non affichable" << std::endl;}
// isdigit - est-ce un chiffre ?if (std::isdigit(c)) { std::cout << c << " est un chiffre" << std::endl;}Manipulateurs I/O : fixed, setprecision
Section intitulée « Manipulateurs I/O : fixed, setprecision »#include <iomanip> // pour setprecision#include <iostream>
double d = 42.0;float f = 42.0f;
// La sortie par défaut pourrait montrer "42" au lieu de "42.0"// Utilisez fixed et setprecision pour contrôler les décimales
std::cout << std::fixed; // Utiliser la notation à point fixestd::cout << std::setprecision(1); // 1 décimale
std::cout << "float: " << f << "f" << std::endl; // "42.0f"std::cout << "double: " << d << std::endl; // "42.0"
// Pour plus de précision :std::cout << std::setprecision(6);std::cout << 3.14159265358979 << std::endl; // "3.141593"Exemple Complet de Sortie ScalarConverter
Section intitulée « Exemple Complet de Sortie ScalarConverter »void displayConversions(double value) { // char if (std::isnan(value) || std::isinf(value) || value < 0 || value > 127) { std::cout << "char: impossible" << std::endl; } else if (!std::isprint(static_cast<char>(value))) { std::cout << "char: Non affichable" << std::endl; } else { std::cout << "char: '" << static_cast<char>(value) << "'" << std::endl; }
// int if (std::isnan(value) || std::isinf(value) || value < INT_MIN || value > INT_MAX) { std::cout << "int: impossible" << std::endl; } else { std::cout << "int: " << static_cast<int>(value) << std::endl; }
// float std::cout << std::fixed << std::setprecision(1); if (std::isnan(value)) std::cout << "float: nanf" << std::endl; else if (std::isinf(value)) std::cout << "float: " << (value > 0 ? "+inff" : "-inff") << std::endl; else std::cout << "float: " << static_cast<float>(value) << "f" << std::endl;
// double if (std::isnan(value)) std::cout << "double: nan" << std::endl; else if (std::isinf(value)) std::cout << "double: " << (value > 0 ? "+inf" : "-inf") << std::endl; else std::cout << "double: " << value << std::endl;}ex01 : Sérialisation
Section intitulée « ex01 : Sérialisation »class Serializer {private: Serializer(); // Non instanciable
public: static uintptr_t serialize(Data* ptr); static Data* deserialize(uintptr_t raw);};
struct Data { int id; std::string name; float value;};
// Implémentationuintptr_t Serializer::serialize(Data* ptr) { return reinterpret_cast<uintptr_t>(ptr);}
Data* Serializer::deserialize(uintptr_t raw) { return reinterpret_cast<Data*>(raw);}
// TestData original = {42, "test", 3.14f};uintptr_t serialized = Serializer::serialize(&original);Data* deserialized = Serializer::deserialize(serialized);// deserialized devrait == &originalex02 : Identification de Type
Section intitulée « ex02 : Identification de Type »class Base {public: virtual ~Base() {}};
class A : public Base { };class B : public Base { };class C : public Base { };
// Génère aléatoirement A, B, ou CBase* generate() { srand(time(NULL)); switch (rand() % 3) { case 0: return new A(); case 1: return new B(); case 2: return new C(); } return NULL;}Génération de Nombres Aléatoires
Section intitulée « Génération de Nombres Aléatoires »#include <cstdlib> // pour srand, rand#include <ctime> // pour time
// Initialiser le générateur de nombres aléatoires (faire une fois au début du programme)std::srand(std::time(NULL)); // Utiliser l'heure actuelle comme seed
// Générer des nombres aléatoiresint random = std::rand(); // 0 à RAND_MAXint random0to9 = std::rand() % 10; // 0 à 9int random1to6 = std::rand() % 6 + 1; // 1 à 6 (lancer de dé)
// Pour RobotomyRequestForm - taux de succès de 50%bool success = (std::rand() % 2 == 0);if (success) { std::cout << _target << " a été robotomisé avec succès" << std::endl;} else { std::cout << "La robotomisation de " << _target << " a échoué" << std::endl;}
// Identifier en utilisant un pointeur (retourne NULL en cas d'échec)void identify(Base* p) { if (dynamic_cast<A*>(p)) std::cout << "A" << std::endl; else if (dynamic_cast<B*>(p)) std::cout << "B" << std::endl; else if (dynamic_cast<C*>(p)) std::cout << "C" << std::endl;}
// Identifier en utilisant une référence (lance une exception en cas d'échec)void identify(Base& p) { try { (void)dynamic_cast<A&>(p); std::cout << "A" << std::endl; return; } catch (...) {}
try { (void)dynamic_cast<B&>(p); std::cout << "B" << std::endl; return; } catch (...) {}
try { (void)dynamic_cast<C&>(p); std::cout << "C" << std::endl; } catch (...) {}}10. Guide de Sélection des Casts
Section intitulée « 10. Guide de Sélection des Casts »| Situation | Cast à Utiliser |
|---|---|
| Conversion numérique (int <-> double) | static_cast |
| Upcasting (Derived* -> Base*) | static_cast |
| Downcasting (Base* -> Derived*) avec virtual | dynamic_cast |
| Retirer/ajouter const | const_cast |
| Pointeur <-> entier | reinterpret_cast |
| Types de pointeurs non reliés | reinterpret_cast |
| Type-punning (lecture d’octets) | reinterpret_cast |
Exercice 00 : Conversion de Types Scalaires
Section intitulée « Exercice 00 : Conversion de Types Scalaires »Analyse du Sujet
Section intitulée « Analyse du Sujet »Construisez une classe ScalarConverter avec une méthode statique qui :
- Prend un littéral string représentant une valeur littérale C++
- Détecte de quel type est le littéral (char, int, float, double)
- Convertit et affiche la valeur dans les quatre types scalaires
- Gère les valeurs spéciales :
nan,nanf,inf,inff,+inf,-inf, etc.
Contraintes clés :
- La classe doit être non-instanciable (méthode statique uniquement)
- Doit gérer les cas limites : overflow, chars non affichables
- Format d’affichage : suffixe
.0pour les valeurs entières float/double
Format de sortie attendu :
char: 'a'int: 97float: 97.0fdouble: 97.0Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Réfléchissez avant de coder :
-
Quels types d’entrée pouvons-nous recevoir ?
- Caractère unique :
'a','*' - Entier :
42,-17,0 - Float :
42.0f,-4.2f,nanf,inff - Double :
42.0,-4.2,nan,inf
- Caractère unique :
-
Comment détecter le type ?
- char : caractère unique non-chiffre
- int : tous des chiffres avec signe optionnel
- float : a le suffixe ‘f’ (ou spécial : nanf, inff)
- double : a un point décimal sans ‘f’ (ou spécial : nan, inf)
-
L’ordre de détection compte :
1. Valeurs spéciales d'abord (variantes nan, inf)2. Char unique (mais pas un chiffre)3. Entier (tous des chiffres)4. Float (se termine par 'f')5. Double (a une décimale) -
Qu’est-ce qui peut mal tourner dans la conversion ?
- int overflow → “impossible”
- char hors plage (0-127) → “impossible”
- char non-imprimable → “Non affichable”
- inf/nan → certaines conversions impossibles
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 : Structure de classe (non-instanciable)
#ifndef SCALARCONVERTER_HPP#define SCALARCONVERTER_HPP
#include <string>
class ScalarConverter {private: // Constructeur privé = ne peut pas instancier ScalarConverter(); ScalarConverter(const ScalarConverter& other); ScalarConverter& operator=(const ScalarConverter& other); ~ScalarConverter();
public: static void convert(const std::string& literal);};
#endifÉtape 2 : Helpers de détection de type
#include "ScalarConverter.hpp"#include <cstdlib> // strtod, strtol#include <cctype> // isdigit, isprint#include <climits> // INT_MIN, INT_MAX#include <cmath> // isnan, isinf#include <iostream>#include <iomanip> // setprecision
// Helpers privés (dans un namespace anonyme ou en static privé)static bool isSpecialFloat(const std::string& s) { return s == "nanf" || s == "inff" || s == "+inff" || s == "-inff";}
static bool isSpecialDouble(const std::string& s) { return s == "nan" || s == "inf" || s == "+inf" || s == "-inf";}
static bool isChar(const std::string& s) { // Caractère unique qui N'EST PAS un chiffre return s.length() == 1 && !std::isdigit(s[0]);}
static bool isInt(const std::string& s) { if (s.empty()) return false; size_t start = 0; if (s[0] == '+' || s[0] == '-') start = 1; if (start >= s.length()) return false;
for (size_t i = start; i < s.length(); i++) { if (!std::isdigit(s[i])) return false; } return true;}
static bool isFloat(const std::string& s) { if (s.empty() || s[s.length() - 1] != 'f') return false; std::string withoutF = s.substr(0, s.length() - 1); char* end; std::strtod(withoutF.c_str(), &end); return *end == '\0' && withoutF.find('.') != std::string::npos;}
static bool isDouble(const std::string& s) { if (s.empty()) return false; char* end; std::strtod(s.c_str(), &end); return *end == '\0' && s.find('.') != std::string::npos;}Étape 3 : Conversion et affichage
static void printChar(double d) { if (std::isnan(d) || std::isinf(d) || d < 0 || d > 127) std::cout << "char: impossible" << std::endl; else if (!std::isprint(static_cast<int>(d))) std::cout << "char: Non affichable" << std::endl; else std::cout << "char: '" << static_cast<char>(d) << "'" << std::endl;}
static void printInt(double d) { if (std::isnan(d) || std::isinf(d) || d < INT_MIN || d > INT_MAX) std::cout << "int: impossible" << std::endl; else std::cout << "int: " << static_cast<int>(d) << std::endl;}
static void printFloatDouble(double d) { std::cout << std::fixed << std::setprecision(1); std::cout << "float: " << static_cast<float>(d) << "f" << std::endl; std::cout << "double: " << d << std::endl;}
static void fromDouble(double d) { printChar(d); printInt(d); printFloatDouble(d);}Étape 4 : Fonction convert principale
void ScalarConverter::convert(const std::string& literal) { // Gérer les valeurs float spéciales if (isSpecialFloat(literal)) { std::string withoutF = literal.substr(0, literal.length() - 1); double d = std::strtod(withoutF.c_str(), NULL); fromDouble(d); return; }
// Gérer les valeurs double spéciales if (isSpecialDouble(literal)) { double d = std::strtod(literal.c_str(), NULL); fromDouble(d); return; }
// Gérer char if (isChar(literal)) { fromDouble(static_cast<double>(literal[0])); return; }
// Gérer int if (isInt(literal)) { long l = std::strtol(literal.c_str(), NULL, 10); if (l < INT_MIN || l > INT_MAX) { std::cout << "char: impossible" << std::endl; std::cout << "int: impossible" << std::endl; std::cout << "float: impossible" << std::endl; std::cout << "double: impossible" << std::endl; return; } fromDouble(static_cast<double>(l)); return; }
// Gérer float if (isFloat(literal)) { std::string withoutF = literal.substr(0, literal.length() - 1); double d = std::strtod(withoutF.c_str(), NULL); fromDouble(d); return; }
// Gérer double if (isDouble(literal)) { double d = std::strtod(literal.c_str(), NULL); fromDouble(d); return; }
// Entrée invalide std::cout << "char: impossible" << std::endl; std::cout << "int: impossible" << std::endl; std::cout << "float: impossible" << std::endl; std::cout << "double: impossible" << std::endl;}Pièges Courants
Section intitulée « Pièges Courants »1. Mauvais ordre de détection :
// FAUX : "42" détecté comme double avant intif (isDouble(s)) ... // Match "42" !
// CORRECT : Vérifier int avant doubleif (isInt(s)) ...if (isDouble(s)) ...2. Format de précision manquant :
// FAUX : Affiche "42f" et "42"std::cout << "float: " << 42.0f << "f" << std::endl;
// CORRECT : Affiche "42.0f" et "42.0"std::cout << std::fixed << std::setprecision(1);std::cout << "float: " << 42.0f << "f" << std::endl;Conseils de Test
Section intitulée « Conseils de Test »Script de test :
# Types de base./convert 'a' # char./convert 42 # int./convert 42.0f # float./convert 42.0 # double
# Cas limites./convert 0 # Zéro./convert -42 # Négatif./convert 127 # Char max./convert 128 # Char impossible
# Valeurs spéciales./convert nan./convert nanf./convert inf./convert inff./convert +inf./convert -inff
# Non affichable./convert 0 # Non affichable (caractère null)./convert 31 # Non affichable (caractère de contrôle)
# Invalide./convert "" # Vide./convert "hello" # Littéral invalide./convert "42.42.42" # Décimales multiplesExercice 01 : Sérialisation
Section intitulée « Exercice 01 : Sérialisation »Analyse du Sujet
Section intitulée « Analyse du Sujet »Construisez une classe Serializer qui :
- Convertit un pointeur
Data*enuintptr_t(serialize) - Convertit un
uintptr_tretour enData*(deserialize) - Doit être non-instanciable (méthodes statiques uniquement)
- Vous devez créer une struct
Datanon vide
Idée clé : Cet exercice enseigne reinterpret_cast - le cast le plus dangereux qui réinterprète le pattern de bits.
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Réfléchissez avant de coder :
-
Qu’est-ce que uintptr_t ?
- Un type entier non signé garanti de contenir n’importe quel pointeur
- Défini dans
<cstdint>(C++11) ou<stdint.h>(C99) - Pour C++98 : utilisez
<stdint.h>ou cast versunsigned long
-
Pourquoi reinterpret_cast ?
- Pointeur ↔ entier n’est pas une conversion de type
- C’est une réinterprétation au niveau des bits
static_castne fonctionnera pas pour cela
-
Que tester ?
- Aller-retour :
deserialize(serialize(ptr)) == ptr - Intégrité des données : valeurs dans la struct inchangées
- Aller-retour :
Code Final
Section intitulée « Code Final »#ifndef DATA_HPP#define DATA_HPP
#include <string>
struct Data { int id; std::string name; double value;};
#endif#ifndef SERIALIZER_HPP#define SERIALIZER_HPP
#include <stdint.h>#include "Data.hpp"
class Serializer {private: Serializer(); Serializer(const Serializer& other); Serializer& operator=(const Serializer& other); ~Serializer();
public: static uintptr_t serialize(Data* ptr); static Data* deserialize(uintptr_t raw);};
#endif#include "Serializer.hpp"
Serializer::Serializer() {}Serializer::Serializer(const Serializer& other) { (void)other; }Serializer& Serializer::operator=(const Serializer& other) { (void)other; return *this; }Serializer::~Serializer() {}
uintptr_t Serializer::serialize(Data* ptr) { return reinterpret_cast<uintptr_t>(ptr);}
Data* Serializer::deserialize(uintptr_t raw) { return reinterpret_cast<Data*>(raw);}Exercice 02 : Identifier le Type Réel
Section intitulée « Exercice 02 : Identifier le Type Réel »Analyse du Sujet
Section intitulée « Analyse du Sujet »Créez une classe Base avec des classes dérivées A, B, C. Implémentez :
Base* generate()- retourne aléatoirement un new A, B, ou Cvoid identify(Base* p)- affiche le type réel en utilisant un pointeurvoid identify(Base& p)- affiche le type réel en utilisant une référence
Contraintes clés :
- Le header
<typeinfo>est INTERDIT - La version référence ne peut pas utiliser de pointeurs en interne
- Base doit avoir un destructeur virtuel
- Base, A, B, C n’ont pas besoin de suivre la Forme Canonique Orthodoxe (OCF) pour cet exercice
Code Final
Section intitulée « Code Final »#ifndef BASE_HPP#define BASE_HPP
class Base {public: virtual ~Base();};
#endif#include "Base.hpp"#include "A.hpp"#include "B.hpp"#include "C.hpp"#include <cstdlib>#include <ctime>#include <iostream>
Base* generate() { static bool seeded = false; if (!seeded) { std::srand(std::time(NULL)); seeded = true; } switch (std::rand() % 3) { case 0: return new A(); case 1: return new B(); case 2: return new C(); } return NULL;}
void identify(Base* p) { if (dynamic_cast<A*>(p)) std::cout << "A" << std::endl; else if (dynamic_cast<B*>(p)) std::cout << "B" << std::endl; else if (dynamic_cast<C*>(p)) std::cout << "C" << std::endl; else std::cout << "Inconnu" << std::endl;}
void identify(Base& p) { try { (void)dynamic_cast<A&>(p); std::cout << "A" << std::endl; return; } catch (...) {} try { (void)dynamic_cast<B&>(p); std::cout << "B" << std::endl; return; } catch (...) {} try { (void)dynamic_cast<C&>(p); std::cout << "C" << std::endl; return; } catch (...) {} std::cout << "Inconnu" << std::endl;}Référence Rapide
Section intitulée « Référence Rapide »// static_cast - compilation, types reliésint i = static_cast<int>(3.14);Derived* d = static_cast<Derived*>(base_ptr); // Non vérifié !
// dynamic_cast - exécution, downcast polymorpheDerived* d = dynamic_cast<Derived*>(base_ptr); // NULL si échoueDerived& r = dynamic_cast<Derived&>(base_ref); // lance si échoue
// const_cast - ajouter/retirer constint* p = const_cast<int*>(const_ptr);
// reinterpret_cast - réinterprétation au niveau des bitsuintptr_t addr = reinterpret_cast<uintptr_t>(ptr);Résumé du Module 06 : Les Quatre Casts C++
Section intitulée « Résumé du Module 06 : Les Quatre Casts C++ »| Cast | Objectif | Exercice |
|---|---|---|
static_cast | Conversions numériques sûres | Ex00 : char/int/float/double |
reinterpret_cast | Réinterprétation au niveau bit | Ex01 : pointeur ↔ entier |
dynamic_cast | Identification de type runtime | Ex02 : downcast polymorphe |
const_cast | Retirer/ajouter const | Non couvert (rarement nécessaire) |
Quand utiliser chacun :
| Besoin | Utiliser |
|---|---|
| Convertir entre types numériques | static_cast<cible>(valeur) |
| Upcast (dérivé → base) | Implicite ou static_cast |
| Downcast (base → dérivé) | dynamic_cast (plus sûr) ou static_cast (si sûr) |
| Pointeur ↔ entier | reinterpret_cast |
| Retirer const | const_cast (éviter si possible) |
Concepts Reliés
Section intitulée « Concepts Reliés »Continuez votre voyage C++ :
- Précédent : Module 05 : Exceptions - Revoir la gestion des erreurs
- Suivant : Module 07 : Templates - Apprendre la programmation générique
Termes Clés de Ce Module
Section intitulée « Termes Clés de Ce Module »Visitez le Glossaire pour les définitions de :