Dynamic Casting & RTTI
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 62 von 104.
Manchmal musst du bei der Arbeit mit Polymorphie den tatsächlichen Typ eines Objekts zur Laufzeit bestimmen oder einen Zeiger auf eine Basisklasse sicher in einen Zeiger auf eine abgeleitete Klasse umwandeln. C++ stellt für diese Situationen RTTI (Runtime Type Information) und dynamic_cast bereit.
dynamic_cast konvertiert Zeiger oder Referenzen innerhalb einer Vererbungshierarchie sicher. Anders als static_cast führt es eine Laufzeitprüfung durch und gibt nullptr zurück, wenn die Konvertierung ungültig ist:
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); // Erfolgreich: gibt gültigen Zeiger zurück
if (dog) {
dog->bark(); // Sicher, die Dog-spezifische Methode aufzurufen
}
Cat* cat = dynamic_cast<Cat*>(animal); // Schlägt fehl: gibt nullptr zurückWichtig: dynamic_cast funktioniert nur mit polymorphen Typen (Klassen mit mindestens einer virtuellen Funktion). Mit dem Operator typeid kannst du den tatsächlichen Typ eines Objekts abfragen:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Gibt Typinfo für Dog ausObwohl dynamic_cast nützlich ist, weist eine häufige Verwendung oft auf ein Designproblem hin. Bevorzuge nach Möglichkeit virtuelle Funktionen, da sie dem Objekt ermöglichen, typspezifisches Verhalten ohne explizite Typprüfung zu handhaben.
Aufgabe
EinfachErstellen wir ein Fahrzeuginspektionssystem, das dynamic_cast verwendet, um verschiedene Fahrzeugtypen sicher zu identifizieren und mit ihnen zu interagieren. Du erstellst eine Hierarchie von Fahrzeugen, in der ein Inspektor typspezifische Prüfungen durchführen muss, die nur bestimmte Fahrzeuge unterstützen.
Du organisierst deinen Code über drei Dateien:
Vehicle.h: Definiere eineVehicle-Basisklasse, die jedes Fahrzeug im System repräsentiert:- Ein geschütztes
std::string licensePlate-Member - Einen Konstruktor, der das Kennzeichen initialisiert
- Eine virtuelle
getDescription()-Methode, die"Vehicle: <licensePlate>"zurückgibt - Einen virtuellen Destruktor
- Ein geschütztes
Vehicles.h: Definiere drei abgeleitete Fahrzeugtypen:Car:- Ein privates
int seatCount-Member - Einen Konstruktor, der Kennzeichen und Sitzanzahl entgegennimmt
- Überschreibe
getDescription(), sodass"Car: <licensePlate>"zurückgegeben wird - Eine Methode
inspectSeatbelts(), die Folgendes ausgibt:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- Ein privates
double cargoCapacity-Member (in Tonnen) - Einen Konstruktor, der Kennzeichen und Ladekapazität entgegennimmt
- Überschreibe
getDescription(), sodass"Truck: <licensePlate>"zurückgegeben wird - Eine Methode
inspectCargo(), die Folgendes ausgibt:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- Ein privates
bool hasSidecar-Member - Einen Konstruktor, der Kennzeichen und den Beiwagenstatus entgegennimmt
- Überschreibe
getDescription(), sodass"Motorcycle: <licensePlate>"zurückgegeben wird - Eine Methode
inspectHelmetStorage(), dieInspecting helmet storage in <licensePlate>ausgibt, wenn das Fahrzeug einen Beiwagen hat, oderNo helmet storage in <licensePlate>, wenn es keinen hat
- Ein privates
main.cpp: Lies drei Eingaben ein (jeweils in einer eigenen Zeile):- Kennzeichen des Autos
- Kennzeichen des Lastwagens
- Kennzeichen des Motorrads
Erstelle ein
Carmit 4 Sitzen, einenTruckmit einer Kapazität von 10.5 Tonnen und einMotorcyclemit einem Beiwagen. Speichere alle drei in einem Array ausVehicle*-Zeigern.Durchlaufe das Array und führe für jedes Fahrzeug Folgendes aus:
- Gib seine Beschreibung mithilfe von
getDescription()aus - Verwende
dynamic_cast, um eine Umwandlung in jeden abgeleiteten Typ zu versuchen - Wenn die Umwandlung in
Car*erfolgreich ist, rufeinspectSeatbelts()auf - Wenn die Umwandlung in
Truck*erfolgreich ist, rufeinspectCargo()auf - Wenn die Umwandlung in
Motorcycle*erfolgreich ist, rufeinspectHelmetStorage()auf
Gib zwischen den Inspektionen der einzelnen Fahrzeuge eine Leerzeile aus. Räume die dynamisch reservierten Objekte anschließend auf.
Bei den Eingaben ABC-123, TRK-456 und MTR-789 sieht das Ergebnis beispielsweise so aus:
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-789Beachte, wie dynamic_cast nur dann einen gültigen Zeiger zurückgibt, wenn der tatsächliche Objekttyp mit dem Zieltyp übereinstimmt. Für jedes Fahrzeug ist nur eine der drei Umwandlungen erfolgreich, sodass du die typspezifische Inspektionsmethode sicher aufrufen kannst. Das ist die Stärke von RTTI: den tatsächlichen Typ zur Laufzeit zu bestimmen und entsprechend zu handeln.
Probier es selbst
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// Eingaben lesen
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: Erstelle ein Car mit 4 Sitzen
// TODO: Erstelle einen Truck mit 10.5 Tonnen Kapazität
// TODO: Erstelle ein Motorcycle mit einem Beiwagen (true)
// TODO: Speichere alle drei in einem Array von Vehicle*-Zeigern
// TODO: Durchlaufe das Array und für jedes vehicle:
// 1. Gib seine Beschreibung mit getDescription() aus
// 2. Verwende dynamic_cast, um das Casting zu jedem abgeleiteten Typ zu versuchen
// 3. Wenn das Casting zu Car* erfolgreich ist, rufe inspectSeatbelts() auf
// 4. Wenn das Casting zu Truck* erfolgreich ist, rufe inspectCargo() auf
// 5. If cast to Motorcycle* succeeds, call inspectHelmetStorage()
// 6. Gib eine Leerzeile zwischen der Inspektion jedes Fahrzeugs aus
// TODO: Räume dynamisch allozierte Objekte auf
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler