Dynamic Casting & RTTI
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 62 of 104.
Sometimes when working with polymorphism, you need to determine the actual type of an object at runtime or safely convert a base class pointer to a derived class pointer. C++ provides RTTI (Runtime Type Information) and dynamic_cast for these situations.
dynamic_cast safely converts pointers or references within an inheritance hierarchy. Unlike static_cast, it performs a runtime check and returns nullptr if the conversion is invalid:
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); // Succeeds: returns valid pointer
if (dog) {
dog->bark(); // Safe to call Dog-specific method
}
Cat* cat = dynamic_cast<Cat*>(animal); // Fails: returns nullptrImportant: dynamic_cast only works with polymorphic types (classes with at least one virtual function). The typeid operator lets you query an object's actual type:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Outputs type info for DogWhile dynamic_cast is useful, frequent use often indicates a design issue. Prefer virtual functions when possible, as they let the object handle type-specific behavior without explicit type checking.
Challenge
EasyLet's build a vehicle inspection system that uses dynamic_cast to safely identify and interact with different vehicle types. You'll create a hierarchy of vehicles where an inspector needs to perform type-specific checks that only certain vehicles support.
You'll organize your code across three files:
Vehicle.h: Define a baseVehicleclass that represents any vehicle in the system:- A protected
std::string licensePlatemember - A constructor that initializes the license plate
- A virtual
getDescription()method that returns"Vehicle: <licensePlate>" - A virtual destructor
- A protected
Vehicles.h: Define three derived vehicle types:Car:- A private
int seatCountmember - A constructor taking license plate and seat count
- Override
getDescription()to return"Car: <licensePlate>" - A method
inspectSeatbelts()that prints:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- A private
double cargoCapacitymember (in tons) - A constructor taking license plate and cargo capacity
- Override
getDescription()to return"Truck: <licensePlate>" - A method
inspectCargo()that prints:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- A private
bool hasSidecarmember - A constructor taking license plate and sidecar status
- Override
getDescription()to return"Motorcycle: <licensePlate>" - A method
inspectHelmetStorage()that prints:Inspecting helmet storage in <licensePlate>if it has a sidecar, orNo helmet storage in <licensePlate>if it doesn't
- A private
main.cpp: Read three inputs (each on a separate line):- Car license plate
- Truck license plate
- Motorcycle license plate
Create a
Carwith 4 seats, aTruckwith 10.5 tons capacity, and aMotorcyclewith a sidecar. Store all three in an array ofVehicle*pointers.Loop through the array and for each vehicle:
- Print its description using
getDescription() - Use
dynamic_castto attempt casting to each derived type - If the cast to
Car*succeeds, callinspectSeatbelts() - If the cast to
Truck*succeeds, callinspectCargo() - If the cast to
Motorcycle*succeeds, callinspectHelmetStorage()
Print a blank line between each vehicle's inspection. Clean up your dynamically allocated objects when done.
For example, with inputs ABC-123, TRK-456, and 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-789Notice how dynamic_cast returns a valid pointer only when the actual object type matches the target type. For each vehicle, only one of the three casts will succeed, allowing you to safely call the type-specific inspection method. This is the power of RTTI — determining the actual type at runtime and acting accordingly.
Cheat sheet
C++ provides RTTI (Runtime Type Information) and dynamic_cast to determine the actual type of an object at runtime and safely convert pointers within an inheritance hierarchy.
dynamic_cast performs a runtime check and returns nullptr if the conversion is invalid:
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 nullptrImportant: dynamic_cast only works with polymorphic types (classes with at least one virtual function).
The typeid operator queries an object's actual type:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Outputs type info for DogWhile useful, frequent use of dynamic_cast often indicates a design issue. Prefer virtual functions when possible.
Try it yourself
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// Read inputs
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: Create a Car with 4 seats
// TODO: Create a Truck with 10.5 tons capacity
// TODO: Create a Motorcycle with a sidecar (true)
// TODO: Store all three in an array of Vehicle* pointers
// TODO: Loop through the array and for each vehicle:
// 1. Print its description using getDescription()
// 2. Use dynamic_cast to attempt casting to each derived type
// 3. If cast to Car* succeeds, call inspectSeatbelts()
// 4. If cast to Truck* succeeds, call inspectCargo()
// 5. If cast to Motorcycle* succeeds, call inspectHelmetStorage()
// 6. Print a blank line between each vehicle's inspection
// TODO: Clean up dynamically allocated objects
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container