🏛️Go deeper — read the bookObject-Oriented Programming in C++— runnable code & full walkthrough →

oops · high

Upcasting, Downcasting, dynamic_cast & RTTI

Moving around a class hierarchy safely: implicit upcasting, checked downcasting with dynamic_cast (nullptr vs bad_cast), why the type must be polymorphic, how it differs from an unchecked static_cast, what RTTI/typeid give you, and why reaching for dynamic_cast often signals a missing virtual function.

🔑 Key line

Upcasting derived->base is implicit and always safe. Downcasting base->derived needs a CHECKED `dynamic_cast` (requires a polymorphic type); it returns nullptr for pointers / throws bad_cast for references on a wrong type. `static_cast` downcasts are unchecked (UB if wrong). dynamic_cast & typeid use RTTI. Frequent downcasting is usually a design smell — prefer a virtual call.

The code

struct Animal {
virtual ~Animal() = default;
}; // polymorphic (has a vtable)
struct Dog : Animal {
void bark();
};
struct Cat : Animal {};
Animal* a = new Dog; // upcast: implicit, always safe
Dog* d = dynamic_cast<Dog*>(a); // downcast: checked -> non-null
Cat* c = dynamic_cast<Cat*>(a); // checked -> nullptr (not a Cat)
Dog& r = dynamic_cast<Dog&>(*a); // reference form -> throws on fail
Dog* s = static_cast<Dog*>(a); // UNCHECKED -> UB if a isn't a Dog

What this lesson walks through

  1. 01Upcast — derived → base is implicit and always safe
  2. 02Downcast to the real type → dynamic_cast succeeds
  3. 03Wrong type → nullptr (ptr) / throw (ref); static_cast = UB

An object's real type is Dog. Storing it in an Animal* (upcast) is implicit and always safe: a Dog IS-A Animal. The pointer's static type narrows; the object is unchanged.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of Upcasting, Downcasting, dynamic_cast & RTTI and 100+ animated C++ interview lessons.

← Previous
Object Slicing
Next →
Constructor inheritance & initialization order