Aller au contenu

Module 06 : Casts C++

Télécharger le PDF officiel du sujet

Concepts Clés :

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Identification de type
  • Conversions définies par l’utilisateur
  • Le mot-clé explicit

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.


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 explicite

Deux opérations fondamentalement différentes :

  • Conversion : Transforme la valeur (ex: 3.14 devient 3)
  • 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.


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 :

int i = static_cast<int>(3.14); // Convertit double en int
Base* 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)
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 NULL pour les pointeurs si le cast échoue
  • Lance une exception std::bad_cast pour 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)
int* p = const_cast<int*>(const_ptr); // Retire const
const int* cp = const_cast<const int*>(p); // Ajoute const
  • const_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
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
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)

// Les casts de style C font tout - trop puissants, pas de clarté
int x = (int)3.14; // OK
int* p = (int*)&x; // Dangereux
const int* cp = &x;
int* mp = (int*)cp; // Retire const - dangereux !
  • 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

Conversions vérifiées à la compilation entre types reliés.

1. Conversions numériques

double d = 3.14;
int i = static_cast<int>(d); // 3
float f = static_cast<float>(i); // 3.0f

2. Enum vers int (et vice versa)

enum Color { RED, GREEN, BLUE };
Color c = static_cast<Color>(1); // GREEN
int n = static_cast<int>(c); // 1

3. 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);
// Types non reliés - ne compile pas
double* dp;
int* ip = static_cast<int*>(dp); // ERREUR
// Suppression de const - utilisez const_cast
const int* cp;
int* p = static_cast<int*>(cp); // ERREUR

Downcasts vérifiés à l’exécution dans les hiérarchies de classes polymorphes.

  • 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 RTTI
class 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);
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
}
Base& br = derived_object;
try {
Derived& dr = dynamic_cast<Derived&>(br);
// Cast réussi
}
catch (std::bad_cast& e) {
// Cast échoué
}
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
}

Ajouter ou retirer les qualificateurs const/volatile.

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);
// L'original était non-const, temporairement rendu const
int x = 42;
const int* cp = &x;
int* p = const_cast<int*>(cp); // Sûr - x n'a jamais été const
*p = 100; // OK
const int CONSTANT = 42;
int* p = const_cast<int*>(&CONSTANT);
*p = 100; // COMPORTEMENT INDÉFINI ! CONSTANT est vraiment const

Réinterprétation de bas niveau des patterns de bits. Cast le plus dangereux.

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 d
  • 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;
}
};
// Utilisation
Fraction half(1, 2);
double d = half; // Appelle operator double(), d = 0.5
if (half) { // Appelle operator bool()
std::cout << "Fraction non-zéro" << std::endl;
}
  • 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

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 !
class String {
public:
explicit String(int size) { // Marque le constructeur explicit
_data = new char[size];
_size = size;
}
};
printString(42); // Erreur ! Pas de conversion implicite
printString(String(42)); // OK - construction explicite

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
};

Les casts C++ varient dans ce qu’ils vérifient :

CastNiveau de VérificationSécurité
dynamic_castExécutionLe plus sûr
static_castCompilationModéré
const_castCompilationDépend du contexte
reinterpret_castAucunLe plus dangereux

Choisissez le cast le moins permissif qui fonctionne pour votre situation.


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"
}
#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 conversion
if (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 entiers
long intValue = std::strtol(str, &endptr, 10); // base 10
#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'infini
if (std::isinf(value)) {
if (value > 0)
std::cout << "La valeur est +inf" << std::endl;
else
std::cout << "La valeur est -inf" << std::endl;
}
#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;
}
#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 fixe
std::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"
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;
}
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émentation
uintptr_t Serializer::serialize(Data* ptr) {
return reinterpret_cast<uintptr_t>(ptr);
}
Data* Serializer::deserialize(uintptr_t raw) {
return reinterpret_cast<Data*>(raw);
}
// Test
Data original = {42, "test", 3.14f};
uintptr_t serialized = Serializer::serialize(&original);
Data* deserialized = Serializer::deserialize(serialized);
// deserialized devrait == &original
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 C
Base* generate() {
srand(time(NULL));
switch (rand() % 3) {
case 0: return new A();
case 1: return new B();
case 2: return new C();
}
return NULL;
}
#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éatoires
int random = std::rand(); // 0 à RAND_MAX
int random0to9 = std::rand() % 10; // 0 à 9
int 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 (...) {}
}

SituationCast à Utiliser
Conversion numérique (int <-> double)static_cast
Upcasting (Derived* -> Base*)static_cast
Downcasting (Base* -> Derived*) avec virtualdynamic_cast
Retirer/ajouter constconst_cast
Pointeur <-> entierreinterpret_cast
Types de pointeurs non reliésreinterpret_cast
Type-punning (lecture d’octets)reinterpret_cast

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 .0 pour les valeurs entières float/double

Format de sortie attendu :

char: 'a'
int: 97
float: 97.0f
double: 97.0

Réfléchissez avant de coder :

  1. 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
  2. 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)
  3. 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)
  4. 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

Étape 1 : Structure de classe (non-instanciable)

ScalarConverter.hpp
#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

ScalarConverter.cpp - détection
#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

ScalarConverter.cpp - conversion
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

ScalarConverter.cpp - convert
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;
}

1. Mauvais ordre de détection :

// FAUX : "42" détecté comme double avant int
if (isDouble(s)) ... // Match "42" !
// CORRECT : Vérifier int avant double
if (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;

Script de test :

Fenêtre de terminal
# 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 multiples

Construisez une classe Serializer qui :

  • Convertit un pointeur Data* en uintptr_t (serialize)
  • Convertit un uintptr_t retour en Data* (deserialize)
  • Doit être non-instanciable (méthodes statiques uniquement)
  • Vous devez créer une struct Data non vide

Idée clé : Cet exercice enseigne reinterpret_cast - le cast le plus dangereux qui réinterprète le pattern de bits.

Réfléchissez avant de coder :

  1. 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 vers unsigned long
  2. Pourquoi reinterpret_cast ?

    • Pointeur ↔ entier n’est pas une conversion de type
    • C’est une réinterprétation au niveau des bits
    • static_cast ne fonctionnera pas pour cela
  3. Que tester ?

    • Aller-retour : deserialize(serialize(ptr)) == ptr
    • Intégrité des données : valeurs dans la struct inchangées
Data.hpp
#ifndef DATA_HPP
#define DATA_HPP
#include <string>
struct Data {
int id;
std::string name;
double value;
};
#endif
Serializer.hpp
#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
Serializer.cpp
#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);
}

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 C
  • void identify(Base* p) - affiche le type réel en utilisant un pointeur
  • void 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
Base.hpp
#ifndef BASE_HPP
#define BASE_HPP
class Base {
public:
virtual ~Base();
};
#endif
functions.cpp
#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;
}

// static_cast - compilation, types reliés
int i = static_cast<int>(3.14);
Derived* d = static_cast<Derived*>(base_ptr); // Non vérifié !
// dynamic_cast - exécution, downcast polymorphe
Derived* d = dynamic_cast<Derived*>(base_ptr); // NULL si échoue
Derived& r = dynamic_cast<Derived&>(base_ref); // lance si échoue
// const_cast - ajouter/retirer const
int* p = const_cast<int*>(const_ptr);
// reinterpret_cast - réinterprétation au niveau des bits
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);

CastObjectifExercice
static_castConversions numériques sûresEx00 : char/int/float/double
reinterpret_castRéinterprétation au niveau bitEx01 : pointeur ↔ entier
dynamic_castIdentification de type runtimeEx02 : downcast polymorphe
const_castRetirer/ajouter constNon couvert (rarement nécessaire)

Quand utiliser chacun :

BesoinUtiliser
Convertir entre types numériquesstatic_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 ↔ entierreinterpret_cast
Retirer constconst_cast (éviter si possible)

Continuez votre voyage C++ :

Visitez le Glossaire pour les définitions de :