Dynamic Casting & RTTI
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 62 sur 104.
Parfois, lorsque vous travaillez avec le polymorphisme, vous devez déterminer le type réel d'un objet au moment de l'exécution ou convertir en toute sécurité un pointeur de classe de base en un pointeur de classe dérivée. Le C++ fournit le RTTI (Runtime Type Information) et dynamic_cast pour ces situations.
dynamic_cast convertit en toute sécurité des pointeurs ou des références au sein d'une hiérarchie d'héritage. Contrairement à static_cast, il effectue une vérification à l'exécution et retourne nullptr si la conversion est invalide :
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof!" << std::endl; }
};
class Cat : public Animal {};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Réussit : retourne un pointeur valide
if (dog) {
dog->bark(); // Sûr d'appeler une méthode spécifique à Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Échoue : retourne nullptrImportant : dynamic_cast ne fonctionne qu'avec des types polymorphes (classes avec au moins une fonction virtuelle). L'opérateur typeid vous permet d'interroger le type réel d'un objet :
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Affiche les informations de type pour DogBien que dynamic_cast soit utile, une utilisation fréquente indique souvent un problème de conception. Privilégiez les fonctions virtuelles quand cela est possible, car elles permettent à l'objet de gérer le comportement spécifique au type sans vérification de type explicite.
Défi
FacileConstruisons un système d'inspection de véhicules qui utilise dynamic_cast pour identifier et interagir en toute sécurité avec différents types de véhicules. Vous allez créer une hiérarchie de véhicules où un inspecteur doit effectuer des vérifications spécifiques au type que seuls certains véhicules prennent en charge.
Vous organiserez votre code sur trois fichiers :
Vehicle.h: Définissez une classe de baseVehiclequi représente n'importe quel véhicule dans le système :- Un membre protégé
std::string licensePlate - Un constructeur qui initialise la plaque d'immatriculation
- Une méthode virtuelle
getDescription()qui retourne"Vehicle: <licensePlate>" - Un destructeur virtuel
- Un membre protégé
Vehicles.h: Définissez trois types de véhicules dérivés :Car:- Un membre privé
int seatCount - Un constructeur prenant la plaque d'immatriculation et le nombre de sièges
- Surchargez
getDescription()pour retourner"Car: <licensePlate>" - Une méthode
inspectSeatbelts()qui affiche :Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- Un membre privé
double cargoCapacity(en tonnes) - Un constructeur prenant la plaque d'immatriculation et la capacité de chargement
- Surchargez
getDescription()pour retourner"Truck: <licensePlate>" - Une méthode
inspectCargo()qui affiche :Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- Un membre privé
bool hasSidecar - Un constructeur prenant la plaque d'immatriculation et le statut du side-car
- Surchargez
getDescription()pour retourner"Motorcycle: <licensePlate>" - Une méthode
inspectHelmetStorage()qui affiche :Inspecting helmet storage in <licensePlate>s'il a un side-car, ouNo helmet storage in <licensePlate>s'il n'en a pas
- Un membre privé
main.cpp: Lisez trois entrées (chacune sur une ligne séparée) :- Plaque d'immatriculation de la voiture
- Plaque d'immatriculation du camion
- Plaque d'immatriculation de la moto
Créez une
Caravec 4 sièges, unTruckavec une capacité de 10.5 tonnes, et uneMotorcycleavec un side-car. Stockez les trois dans un tableau de pointeursVehicle*.Bouclez sur le tableau et pour chaque véhicule :
- Affichez sa description en utilisant
getDescription() - Utilisez
dynamic_castpour tenter un transtypage vers chaque type dérivé - Si le transtypage vers
Car*réussit, appelezinspectSeatbelts() - Si le transtypage vers
Truck*réussit, appelezinspectCargo() - Si le transtypage vers
Motorcycle*réussit, appelezinspectHelmetStorage()
Affichez une ligne vide entre l'inspection de chaque véhicule. Nettoyez vos objets alloués dynamiquement une fois terminé.
Par exemple, avec les entrées ABC-123, TRK-456, et MTR-789 :
Car: ABC-123
Inspecting 4 seatbelts in ABC-123
Truck: TRK-456
Inspecting cargo area (10.5 tons) in TRK-456
Motorcycle: MTR-789
Inspecting helmet storage in MTR-789Remarquez comment dynamic_cast ne renvoie un pointeur valide que lorsque le type réel de l'objet correspond au type cible. Pour chaque véhicule, un seul des trois transtypages réussira, vous permettant d'appeler en toute sécurité la méthode d'inspection spécifique au type. C'est la puissance du RTTI — déterminer le type réel au moment de l'exécution et agir en conséquence.
Aide-mémoire
Le C++ fournit le RTTI (Runtime Type Information) et dynamic_cast pour déterminer le type réel d'un objet au moment de l'exécution et convertir en toute sécurité les pointeurs au sein d'une hiérarchie d'héritage.
dynamic_cast effectue une vérification à l'exécution et renvoie nullptr si la conversion est invalide :
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof!" << std::endl; }
};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Réussit : renvoie un pointeur valide
if (dog) {
dog->bark(); // Sûr d'appeler une méthode spécifique à Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Échoue : renvoie nullptrImportant : dynamic_cast ne fonctionne qu'avec des types polymorphes (classes ayant au moins une fonction virtuelle).
L'opérateur typeid interroge le type réel d'un objet :
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Affiche les informations de type pour DogBien qu'utile, l'utilisation fréquente de dynamic_cast indique souvent un problème de conception. Préférez les fonctions virtuelles lorsque cela est possible.
Essayez vous-même
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// Lire les entrées
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: Créer une Car avec 4 sièges
// TODO: Créer un Truck avec une capacité de 10.5 tonnes
// TODO: Créer une Motorcycle avec un sidecar (true)
// TODO: Stocker les trois dans un tableau de pointeurs Vehicle*
// TODO: Parcourir le tableau et pour chaque véhicule :
// 1. Afficher sa description en utilisant getDescription()
// 2. Utiliser dynamic_cast pour tenter un transtypage vers chaque type dérivé
// 3. Si le transtypage vers Car* réussit, appeler inspectSeatbelts()
// 4. Si le transtypage vers Truck* réussit, appeler inspectCargo()
// 5. Si le transtypage vers Motorcycle* réussit, appeler inspectHelmetStorage()
// 6. Afficher une ligne vide entre l'inspection de chaque véhicule
// TODO: Libérer les objets alloués dynamiquement
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique