Menu
Coddy logo textTech

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 nullptr

Important: 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 Dog

While 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 icon

Challenge

Easy

Let'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 base Vehicle class that represents any vehicle in the system:
    • A protected std::string licensePlate member
    • A constructor that initializes the license plate
    • A virtual getDescription() method that returns "Vehicle: <licensePlate>"
    • A virtual destructor
  • Vehicles.h: Define three derived vehicle types:

    Car:

    • A private int seatCount member
    • 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 cargoCapacity member (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 hasSidecar member
    • 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, or No helmet storage in <licensePlate> if it doesn't
  • main.cpp: Read three inputs (each on a separate line):
    1. Car license plate
    2. Truck license plate
    3. Motorcycle license plate

    Create a Car with 4 seats, a Truck with 10.5 tons capacity, and a Motorcycle with a sidecar. Store all three in an array of Vehicle* pointers.

    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 the cast to Car* succeeds, call inspectSeatbelts()
    4. If the cast to Truck* succeeds, call inspectCargo()
    5. If the cast to Motorcycle* succeeds, call inspectHelmetStorage()

    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-789

Notice 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 nullptr

Important: 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 Dog

While 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;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming