static_cast vs dynamic_cast in C++
Short answer: static_cast is checked by the compiler and costs nothing at runtime. dynamic_cast checks the actual object type while the program runs and fails safely if you’re wrong.
Derived* d1 = static_cast<Derived*>(basePtr); // trust me, it's a Derived
Derived* d2 = dynamic_cast<Derived*>(basePtr); // check — nullptr if not
The Core Difference
#include <iostream>
class Animal {
public:
virtual ~Animal() = default; // makes the class polymorphic
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof\n"; }
};
class Cat : public Animal {
public:
void meow() { std::cout << "Meow\n"; }
};
int main() {
Animal* a = new Cat(); // a Cat, seen as an Animal
Dog* d1 = static_cast<Dog*>(a); // compiles, but a is NOT a Dog
// d1->bark(); // undefined behaviour — anything can happen
Dog* d2 = dynamic_cast<Dog*>(a); // checks at runtime
if (d2) d2->bark();
else std::cout << "Not a Dog\n"; // this runs
delete a;
}
static_cast asks the compiler “is this conversion plausible?” The answer is yes — Dog does derive from Animal — so it compiles. But the object is really a Cat, and using d1 is undefined behaviour.
dynamic_cast asks at runtime “is this object actually a Dog?” It isn’t, so you get nullptr and a clean branch.
dynamic_cast Needs a Virtual Function
class Base { }; // NOT polymorphic
class Derived : public Base { };
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // compile error
The compiler only stores runtime type information for polymorphic classes — those with at least one virtual function. Adding a virtual destructor fixes it and is good practice anyway:
class Base {
public:
virtual ~Base() = default; // now polymorphic
};
If you’re deleting derived objects through a base pointer you need that virtual destructor regardless — see virtual destructors in C++.
Casting References Throws Instead
There is no null reference, so the reference form throws:
#include <stdexcept>
#include <typeinfo>
try {
Dog& d = dynamic_cast<Dog&>(*a);
d.bark();
} catch (const std::bad_cast& e) {
std::cout << "Bad cast: " << e.what() << '\n';
}
Pointer form → nullptr on failure. Reference form → throws std::bad_cast.
What static_cast Is Actually For
Most static_cast uses have nothing to do with class hierarchies:
double d = 3.9;
int i = static_cast<int>(d); // 3 — numeric conversion
int a = 7, b = 2;
double r = static_cast<double>(a) / b; // 3.5, not 3
enum Colour { Red, Green };
int c = static_cast<int>(Green); // 1
void* raw = malloc(sizeof(int));
int* p = static_cast<int*>(raw); // void* back to a typed pointer
That integer-division case is the one beginners hit most — see C++ integer division.
Upcasting Doesn’t Need a Cast at All
Going up the hierarchy — derived to base — is always safe and implicit:
Dog* d = new Dog();
Animal* a = d; // no cast needed
You only need a cast going down, and that’s exactly where the choice matters.
The Honest Advice
If you find yourself reaching for dynamic_cast often, the design is usually asking for a virtual function instead:
// Instead of this
if (Dog* d = dynamic_cast<Dog*>(a)) d->bark();
else if (Cat* c = dynamic_cast<Cat*>(a)) c->meow();
// Prefer this
class Animal {
public:
virtual ~Animal() = default;
virtual void speak() = 0;
};
a->speak(); // the object decides
That’s the whole point of polymorphism — see virtual functions and polymorphism. dynamic_cast is the escape hatch for when you genuinely can’t restructure.
Quick Reference
static_cast | dynamic_cast | |
|---|---|---|
| Checked at | Compile time | Runtime |
| Runtime cost | None | Small |
| Fails by | Undefined behaviour | nullptr or bad_cast |
| Needs virtual function | No | Yes |
| Numeric conversions | Yes | No |
| Safe downcasting | No | Yes |
| Use when | Type is guaranteed | Type is uncertain |
Take Your C++ Further
If you want inheritance, polymorphism and casting explained properly rather than pieced together, the C++ Better Explained Ebook covers it in plain English. Just $19.
👉 Get the C++ Better Explained Ebook — $19
Related Articles
- C++ Type Casting Explained — all four named casts.
- Virtual Functions and Polymorphism — the alternative to downcasting.
- C++ Virtual Destructor — why your base class needs one.
- C++ Inheritance — base and derived classes.
- C++ Integer Division — the classic static_cast fix.