Dynamic Casting & RTTI
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 62 von 104.
Manchmal, wenn Sie mit Polymorphismus arbeiten, müssen Sie den tatsächlichen Typ eines Objekts zur Laufzeit bestimmen oder einen Basisklassen-Zeiger sicher in einen Zeiger einer abgeleiteten Klasse umwandeln. C++ bietet RTTI (Runtime Type Information) und dynamic_cast für diese Situationen.
dynamic_cast konvertiert sicher Zeiger oder Referenzen innerhalb einer Vererbungshierarchie. Im Gegensatz zu 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 einen gültigen Zeiger zurück
if (dog) {
dog->bark(); // Sicherer Aufruf einer Dog-spezifischen Methode
}
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). Der typeid-Operator ermöglicht es Ihnen, den tatsächlichen Typ eines Objekts abzufragen:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Gibt Typinformationen für Dog ausObwohl dynamic_cast nützlich ist, deutet eine häufige Verwendung oft auf ein Designproblem hin. Bevorzugen Sie virtuelle Funktionen, wann immer möglich, da sie es dem Objekt ermöglichen, typspezifisches Verhalten ohne explizite Typprüfung zu handhaben.
Aufgabe
EinfachLassen Sie uns ein Fahrzeuginspektionssystem bauen, das dynamic_cast verwendet, um verschiedene Fahrzeugtypen sicher zu identifizieren und mit ihnen zu interagieren. Sie erstellen eine Hierarchie von Fahrzeugen, in der ein Inspektor typspezifische Prüfungen durchführen muss, die nur bestimmte Fahrzeuge unterstützen.
Sie werden Ihren Code auf drei Dateien verteilen:
Vehicle.h: Definieren Sie eine BasisklasseVehicle, die jedes Fahrzeug im System repräsentiert:- Ein geschütztes (protected)
std::string licensePlateMitglied - Ein Konstruktor, der das Kennzeichen initialisiert
- Eine virtuelle
getDescription()Methode, die"Vehicle: <licensePlate>"zurückgibt - Ein virtueller Destruktor
- Ein geschütztes (protected)
Vehicles.h: Definieren Sie drei abgeleitete Fahrzeugtypen:Car:- Ein privates
int seatCountMitglied - Ein Konstruktor, der das Kennzeichen und die Anzahl der Sitze entgegennimmt
- Überschreiben Sie
getDescription(), um"Car: <licensePlate>"zurückzugeben - Eine Methode
inspectSeatbelts(), die Folgendes ausgibt:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- Ein privates
double cargoCapacityMitglied (in Tonnen) - Ein Konstruktor, der das Kennzeichen und die Ladekapazität entgegennimmt
- Überschreiben Sie
getDescription(), um"Truck: <licensePlate>"zurückzugeben - Eine Methode
inspectCargo(), die Folgendes ausgibt:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- Ein privates
bool hasSidecarMitglied - Ein Konstruktor, der das Kennzeichen und den Beiwagen-Status entgegennimmt
- Überschreiben Sie
getDescription(), um"Motorcycle: <licensePlate>"zurückzugeben - Eine Methode
inspectHelmetStorage(), dieInspecting helmet storage in <licensePlate>ausgibt, wenn es einen Beiwagen hat, oderNo helmet storage in <licensePlate>, wenn es keinen hat
- Ein privates
main.cpp: Lesen Sie drei Eingaben ein (jede in einer separaten Zeile):- Kennzeichen des Autos
- Kennzeichen des LKWs
- Kennzeichen des Motorrads
Erstellen Sie ein
Carmit 4 Sitzen, einenTruckmit 10.5 Tonnen Kapazität und einMotorcyclemit einem Beiwagen. Speichern Sie alle drei in einem Array vonVehicle*Pointern.Iterieren Sie durch das Array und führen Sie für jedes Fahrzeug Folgendes aus:
- Geben Sie die Beschreibung mit
getDescription()aus - Verwenden Sie
dynamic_cast, um zu versuchen, in jeden abgeleiteten Typ zu casten - Wenn der Cast zu
Car*erfolgreich ist, rufen SieinspectSeatbelts()auf - Wenn der Cast zu
Truck*erfolgreich ist, rufen SieinspectCargo()auf - Wenn der Cast zu
Motorcycle*erfolgreich ist, rufen SieinspectHelmetStorage()auf
Geben Sie zwischen jeder Fahrzeuginspektion eine Leerzeile aus. Bereinigen Sie Ihre dynamisch zugewiesenen Objekte, wenn Sie fertig sind.
Zum Beispiel mit den Eingaben ABC-123, TRK-456 und 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-789Beachten Sie, wie dynamic_cast nur dann einen gültigen Pointer zurückgibt, wenn der tatsächliche Objekttyp mit dem Zieltyp übereinstimmt. Für jedes Fahrzeug wird nur einer der drei Casts erfolgreich sein, was es Ihnen ermöglicht, die typspezifische Inspektionsmethode sicher aufzurufen. Dies ist die Stärke von RTTI — den tatsächlichen Typ zur Laufzeit zu bestimmen und entsprechend zu handeln.
Spickzettel
C++ bietet RTTI (Runtime Type Information) und dynamic_cast, um den tatsächlichen Typ eines Objekts zur Laufzeit zu bestimmen und Zeiger innerhalb einer Vererbungshierarchie sicher zu konvertieren.
dynamic_cast führt 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; }
};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Succeeds: returns valid pointer
if (dog) {
dog->bark(); // Safe to call Dog-specific method
}
Cat* cat = dynamic_cast<Cat*>(animal); // Fails: returns nullptrWichtig: dynamic_cast funktioniert nur mit polymorphen Typen (Klassen mit mindestens einer virtuellen Funktion).
Der typeid-Operator fragt den tatsächlichen Typ eines Objekts ab:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Outputs type info for DogObwohl nützlich, deutet die häufige Verwendung von dynamic_cast oft auf ein Designproblem hin. Bevorzugen Sie nach Möglichkeit virtuelle Funktionen.
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*-Pointern
// TODO: Durchlaufe das Array in einer Schleife und für jedes Fahrzeug:
// 1. Gib dessen Beschreibung mit getDescription() aus
// 2. Verwende dynamic_cast, um das Casting in jeden 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. Wenn das Casting zu Motorcycle* erfolgreich ist, rufe inspectHelmetStorage() auf
// 6. Gib eine Leerzeile zwischen den Inspektionen der einzelnen Fahrzeuge aus
// TODO: Bereinige die dynamisch zugewiesenen Objekte
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 & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container