Question
I have used C-style casts in C and C++ for many years, such as:
MyClass* m = (MyClass*)ptr;
In C++, I also see these alternatives:
MyClass* m = (MyClass*)ptr;
MyClass* m = static_cast<MyClass*>(ptr);
MyClass* m = dynamic_cast<MyClass*>(ptr);
What is the difference between these forms of casting in C++?
When should static_cast be used instead of a C-style cast, and when is dynamic_cast necessary?
What are the safety and runtime behavior differences between them?
Short Answer
By the end of this page, you will understand what type casting means in C++, why C-style casts are usually discouraged, how static_cast performs compile-time checked conversions, and how dynamic_cast safely checks polymorphic types at runtime. You will also learn when each cast is appropriate and how to avoid common casting mistakes.
Concept
In C++, a cast converts a value or pointer from one type to another. This is sometimes necessary, but it can also be dangerous if the conversion is not valid.
The three forms in this question are not equivalent:
- C-style cast:
(MyClass*)ptr static_cast:static_cast<MyClass*>(ptr)dynamic_cast:dynamic_cast<MyClass*>(ptr)
Why this matters
Types are one of the main ways C++ prevents bugs. A cast tells the compiler: "treat this value as another type." If that statement is wrong, your program may behave incorrectly or even crash.
C++ provides named casts because they make your intent clearer and separate safe conversions from dangerous conversions.
1. C-style cast
MyClass* m = (MyClass*)ptr;
This is the old C syntax. In C++, it is generally discouraged because it is too permissive.
A C-style cast may perform several different kinds of conversions behind the scenes, including ones similar to:
static_castconst_castreinterpret_cast
That means it can hide unsafe operations in a short, unclear syntax.
Mental Model
Think of casting like entering a building with different access methods.
- A C-style cast is like using a master key that opens many doors, even doors you should not open. It is powerful, but risky.
- A
static_castis like using the correct badge for a door that the building plan already says you may access. The check happens from the plan, not in real time. - A
dynamic_castis like a security guard checking your identity at the door right now. If you are not the right person, access is denied.
Another way to think about it:
static_castsays: "I know what this is."dynamic_castsays: "Please verify what this really is."- C-style cast says: "Just do something that makes this compile."
Syntax and Examples
Core syntax
// C-style cast
TargetType value = (TargetType)expression;
// static_cast
TargetType value = static_cast<TargetType>(expression);
// dynamic_cast
TargetType value = dynamic_cast<TargetType>(expression);
Example: static_cast with numbers
double price = 19.99;
int whole = static_cast<int>(price);
whole becomes 19. This is a normal compile-time conversion.
Example: upcasting with inheritance
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
};
Dog dog;
Animal* animalPtr = static_cast<Animal*>(&dog);
This is safe because every Dog is an Animal.
Step by Step Execution
Consider this example:
#include <iostream>
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {
public:
void hello() {
std::cout << "Hello from Derived\n";
}
};
int main() {
Base* ptr = new Derived();
Derived* a = static_cast<Derived*>(ptr);
if (a) {
a->hello();
}
Derived* b = dynamic_cast<Derived*>(ptr);
if (b) {
b->hello();
}
delete ptr;
}
What happens step by step
-
Base* ptr = new Derived();- A
Derivedobject is created. - It is stored in a
Base*pointer.
- A
Real World Use Cases
Where static_cast is commonly used
Numeric conversions
double ratio = 3.8;
int count = static_cast<int>(ratio);
Useful when parsing values, formatting output, or converting between numeric types.
Converting void* from low-level APIs
void* raw = get_buffer();
char* bytes = static_cast<char*>(raw);
This appears in older APIs, callback systems, and C libraries.
Upcasting in class hierarchies
Derived d;
Base* base = static_cast<Base*>(&d);
This happens frequently when storing derived objects behind base-class interfaces.
Where dynamic_cast is commonly used
Working with plugin or framework object hierarchies
A framework may give you a Base*, and you may need to check whether it is actually a more specific subtype.
Processing mixed collections of polymorphic objects
Real Codebase Usage
In real C++ projects, developers usually prefer clear and narrow casts.
Common patterns
1. Prefer no cast if design can avoid it
Good class design often removes the need for downcasting entirely.
For example, virtual functions are often better than checking types manually:
class Animal {
public:
virtual ~Animal() = default;
virtual void speak() = 0;
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof\n";
}
};
Instead of casting to Dog, call speak() through the base interface.
2. Use static_cast for explicit, expected conversions
Developers often use static_cast when they want a visible conversion in code review:
Common Mistakes
1. Using static_cast for unsafe downcasting
Broken example:
Animal* animal = new Cat();
Dog* dog = static_cast<Dog*>(animal);
dog->bark(); // Undefined behavior
Why it is wrong:
static_castdoes not verify thatanimalreally points to aDog.
How to avoid it:
Dog* dog = dynamic_cast<Dog*>(animal);
if (dog) {
dog->bark();
}
2. Forgetting that dynamic_cast needs a polymorphic base class
Broken example:
class Base {};
class Derived : public Base {};
Base* ptr = new Derived();
Derived* d = dynamic_cast<Derived*>(ptr); // Error
Why it is wrong:
Comparisons
| Cast type | Checked at compile time | Checked at runtime | Typical use | Risk level |
|---|---|---|---|---|
| C-style cast | Sometimes | No clear runtime safety | Legacy code, old C syntax | High |
static_cast | Yes | No | Numeric conversion, upcasting, known safe conversion | Medium |
dynamic_cast | Yes | Yes | Safe downcasting in polymorphic hierarchies | Lower |
static_cast vs dynamic_cast
| Feature |
|---|
Cheat Sheet
Quick rules
- Prefer named C++ casts over C-style casts.
- Use
static_castfor explicit, expected conversions. - Use
dynamic_castfor safe runtime-checked downcasting. dynamic_castworks with polymorphic base classes.- Failed
dynamic_caston pointers returnsnullptr. - Failed
dynamic_caston references throwsstd::bad_cast.
Syntax
T value = static_cast<T>(expr);
T value = dynamic_cast<T>(expr);
T value = (T)expr; // C-style cast, usually avoid in C++
Safe examples
int x = static_cast<int>(3.14);
Base* b = static_cast<Base*>(&derivedObj);
if (Derived* d = dynamic_cast<Derived*>(basePtr)) {
d->run();
}
Warning signs
FAQ
What is the difference between static_cast and dynamic_cast in C++?
static_cast performs compile-time conversions without checking the real runtime object type. dynamic_cast checks the actual runtime type and is safer for downcasting in inheritance hierarchies.
Is a C-style cast the same as static_cast?
No. A C-style cast can behave like static_cast, but it can also perform more dangerous kinds of conversions. That is why it is less safe and less explicit.
When should I use dynamic_cast?
Use it when you have a base-class pointer or reference and need to safely determine whether the object is really a specific derived type at runtime.
Why does dynamic_cast require a virtual function?
It needs runtime type information, which is available for polymorphic classes. A class becomes polymorphic when it has at least one virtual function.
Does dynamic_cast have a performance cost?
Yes, usually more than static_cast, because it performs a runtime check. In most code, correctness matters more than this small cost unless profiling shows otherwise.
Should I avoid all casts in C++?
Mini Project
Description
Build a small C++ program that stores different animal types through base-class pointers, then demonstrates the difference between safe runtime checking with dynamic_cast and unchecked downcasting with static_cast. This helps you see exactly why dynamic_cast is useful in real inheritance code.
Goal
Create a polymorphic class hierarchy, store mixed objects in a collection, and safely run subtype-specific behavior only when the object really matches the target type.
Requirements
- Create a base class with at least one
virtualfunction. - Create at least two derived classes, such as
DogandCat. - Store derived objects in a container of base-class pointers.
- Use
dynamic_castto detect one specific derived type safely. - Print different output depending on whether the cast succeeds or fails.
Keep learning
Related questions
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Lambda Expressions Explained: What They Are and When to Use Them
Learn what C++ lambda expressions are, why they exist, when to use them, and how they simplify callbacks, algorithms, and local logic.
C++ Value Categories Explained: lvalues, rvalues, xvalues, glvalues, and prvalues
Learn C++ value categories clearly: lvalues, rvalues, xvalues, glvalues, and prvalues, with examples and why C++11 introduced them.