Conversion dynamique et 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 à l’exécution ou convertir en toute sécurité un pointeur de classe de base en pointeur de classe dérivée. C++ fournit la RTTI (Runtime Type Information) et dynamic_cast pour ces situations.
dynamic_cast convertit en toute sécurité les pointeurs ou les références au sein d’une hiérarchie d’héritage. Contrairement à static_cast, il 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; }
};
class Cat : public Animal {};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Réussit : renvoie un pointeur valide
if (dog) {
dog->bark(); // Sûr d'appeler la méthode spécifique à Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Échoue : renvoie nullptrImportant : dynamic_cast ne fonctionne qu’avec des types polymorphes (des classes comportant 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 infos de type pour DogBien que dynamic_cast soit utile, son utilisation fréquente indique souvent un problème de conception. Préfère les fonctions virtuelles lorsque c’est possible, car elles permettent à l’objet de gérer le comportement spécifique au type sans vérification explicite du type.
Défi
FacileConstruisons un système d’inspection de véhicules qui utilise dynamic_cast pour identifier et manipuler en toute sécurité différents types de véhicules. Vous allez créer une hiérarchie de véhicules dans laquelle un inspecteur doit effectuer des vérifications spécifiques au type, que seuls certains véhicules prennent en charge.
Vous allez organiser votre code sur trois fichiers :
Vehicle.h: définir une classe de baseVehiclequi représente n’importe quel véhicule du système :- un membre
std::string licensePlateprotégé - un constructeur qui initialise la plaque d’immatriculation
- une méthode virtuelle
getDescription()qui renvoie"Vehicle: <licensePlate>" - un destructeur virtuel
- un membre
Vehicles.h: définir trois types de véhicules dérivés :Car:- un membre
int seatCountprivé - un constructeur prenant la plaque d’immatriculation et le nombre de sièges
- redéfinir
getDescription()pour renvoyer"Car: <licensePlate>" - une méthode
inspectSeatbelts()qui affiche :Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- un membre
double cargoCapacityprivé (en tonnes) - un constructeur prenant la plaque d’immatriculation et la capacité de chargement
- redéfinir
getDescription()pour renvoyer"Truck: <licensePlate>" - une méthode
inspectCargo()qui affiche :Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- un membre
bool hasSidecarprivé - un constructeur prenant la plaque d’immatriculation et l’indication de la présence d’un side-car
- redéfinir
getDescription()pour renvoyer"Motorcycle: <licensePlate>" - une méthode
inspectHelmetStorage()qui affiche :Inspecting helmet storage in <licensePlate>s’il possède un side-car, ouNo helmet storage in <licensePlate>s’il n’en possède pas
- un membre
main.cpp: lire trois entrées (chacune sur une ligne distincte) :- la plaque d’immatriculation de la voiture
- la plaque d’immatriculation du camion
- la plaque d’immatriculation de la moto
Créer une
Caravec 4 sièges, unTruckd’une capacité de 10.5 tonnes et uneMotorcycleavec un side-car. Stocker les trois véhicules dans un tableau de pointeursVehicle*.Parcourir le tableau et, pour chaque véhicule :
- afficher sa description à l’aide de
getDescription() - utiliser
dynamic_castpour tenter une conversion vers chaque type dérivé - si la conversion vers
Car*réussit, appelerinspectSeatbelts() - si la conversion vers
Truck*réussit, appelerinspectCargo() - si la conversion vers
Motorcycle*réussit, appelerinspectHelmetStorage()
Afficher une ligne vide entre les inspections de chaque véhicule. Libérer les 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 que dynamic_cast renvoie un pointeur valide uniquement lorsque le type réel de l’objet correspond au type cible. Pour chaque véhicule, une seule des trois conversions réussira, ce qui vous permettra d’appeler en toute sécurité la méthode d’inspection spécifique au type. C’est toute la puissance de la RTTI : déterminer le type réel au moment de l’exécution et agir en conséquence.
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 side-car (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 de caster vers chaque type dérivé
// 3. Si le cast vers Car* réussit, appeler inspectSeatbelts()
// 4. Si le cast vers Truck* réussit, appeler inspectCargo()
// 5. If cast to Motorcycle* succeeds, call inspectHelmetStorage()
// 6. Afficher une ligne vide entre l'inspection de chaque véhicule
// TODO: Nettoyer 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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne