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.
2. static_cast
MyClass* m = static_cast<MyClass*>(ptr);
static_cast is used for conversions that are known and checked at compile time.
Typical uses:
- numeric conversions such as
doubletoint - converting
void*to a specific pointer type - upcasting in class hierarchies, such as
Derived*toBase* - downcasting only when you are certain the object is really of the target type
Important: static_cast does not check object type at runtime when downcasting through inheritance.
If you cast a Base* to Derived* using static_cast, and the object is not actually a Derived, the behavior is invalid.
3. dynamic_cast
auto m = dynamic_cast<MyClass*>(ptr);
dynamic_cast is for safe runtime checking in inheritance hierarchies.
It is mainly used when:
- you have a pointer or reference to a base class
- the base class is polymorphic (usually meaning it has at least one
virtualfunction) - you want to know whether the object is really a certain derived type
For pointer casts:
- success: returns a valid pointer
- failure: returns
nullptr
For reference casts:
- success: returns the reference
- failure: throws
std::bad_cast
Key idea
- Use
static_castwhen the conversion is logically valid and does not require runtime type checking. - Use
dynamic_castwhen converting within a polymorphic class hierarchy and you need runtime safety. - Avoid C-style casts in modern C++ because they are less explicit and easier to misuse.
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.
Example: unsafe downcast with static_cast
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() {}
};
class Cat : public Animal {
};
Animal* animal = new Cat();
Dog* dog = static_cast<Dog*>(animal); // Compiles, but unsafe
// dog->bark(); // Undefined behavior if used
This compiles, but the object is actually a Cat, not a Dog.
Example: safe downcast with dynamic_cast
#include <iostream>
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() {
std::cout << "Woof\n";
}
};
class Cat : public Animal {
};
int main() {
Animal* animal = new Cat();
Dog* dog = dynamic_cast<Dog*>(animal);
if (dog) {
dog->bark();
} else {
std::cout << "animal is not a Dog\n";
}
delete animal;
}
Here, dynamic_cast checks the real runtime type. Since animal points to a Cat, the cast fails and returns nullptr.
Example: C-style cast hiding danger
Animal* animal = new Cat();
Dog* dog = (Dog*)animal; // Looks simple, but dangerous
This hides the same risk as the unsafe static_cast, and possibly more.
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. - This is valid because
Derivedinherits fromBase.
- A
-
Derived* a = static_cast<Derived*>(ptr);- The compiler allows the conversion.
- No runtime check is performed here.
- In this case, it happens to be correct because
ptrreally points to aDerived.
-
if (a)ais not null, so the condition is true.- Note: this does not prove the cast was safe. It only means the pointer itself is non-null.
-
a->hello();- This works because the object really is a
Derived.
- This works because the object really is a
-
Derived* b = dynamic_cast<Derived*>(ptr);- Runtime type information is used to check whether
ptrreally refers to aDerived. - It does, so the cast succeeds.
- Runtime type information is used to check whether
-
if (b)bis valid, so the condition is true.
-
b->hello();- This also works.
If the object were not Derived
If ptr pointed to a plain Base object instead:
static_cast<Derived*>(ptr)would still compile, but using the result would be unsafe.dynamic_cast<Derived*>(ptr)would returnnullptr.
That is the main difference: dynamic_cast verifies at runtime, while static_cast does not.
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
std::vector<Animal*> animals;
If you need dog-specific behavior only for Dog objects, dynamic_cast can safely test each item.
Optional subtype behavior
When some objects support extra behavior and others do not, dynamic_cast can detect that at runtime.
Where C-style casts still appear
- legacy C++ codebases
- code originally written in C
- quick examples or old tutorials
In modern C++, named casts are preferred because they are easier to review and safer to maintain.
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:
int index = static_cast<int>(items.size());
This makes narrowing conversions deliberate rather than accidental.
3. Use dynamic_cast when runtime type checks are part of the design
Base* obj = createObject();
if (auto* special = dynamic_cast<SpecialType*>(obj)) {
special->runSpecialLogic();
}
This pattern is common in editor tools, GUI frameworks, compilers, and game engines.
4. Guard clauses after dynamic_cast
auto* dog = dynamic_cast<Dog*>(animal);
if (!dog) {
return;
}
dog->bark();
This keeps the safe path clear and avoids invalid use.
5. Avoid C-style casts in modern code reviews
Many teams treat C-style casts as a code smell because they can hide const removal, reinterpretation, or unsafe hierarchy conversions.
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:
dynamic_castfor downcasting requires runtime type information.- The base class must be polymorphic, usually by having a
virtualfunction.
Fix:
class Base {
public:
virtual ~Base() = default;
};
3. Assuming static_cast returning a non-null pointer means success
Derived* d = static_cast<Derived*>(basePtr);
if (d) {
// This does not prove the object is actually Derived
}
A non-null pointer only means the original pointer was non-null.
4. Using C-style casts because they are shorter
Broken example:
MyClass* m = (MyClass*)ptr;
Why it is a problem:
- It does not communicate what kind of conversion is happening.
- It can silently perform dangerous conversions.
Better:
MyClass* m = static_cast<MyClass*>(ptr);
or
MyClass* m = dynamic_cast<MyClass*>(ptr);
Choose based on whether runtime checking is needed.
5. Overusing dynamic_cast
If you constantly cast between types, your class design may need improvement.
Often a virtual function, interface, or better abstraction is cleaner than repeated runtime type checks.
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 | static_cast | dynamic_cast |
|---|---|---|
| Runtime type check | No | Yes |
| Requires polymorphic base class for downcast | No | Yes |
| Performance cost | Usually lower | Usually higher |
| Safe for uncertain downcast | No | Yes |
Returns nullptr on failed pointer cast | No | Yes |
C-style cast vs C++ named casts
| Style | Benefit | Drawback |
|---|---|---|
| C-style cast | Short | Too ambiguous and can hide unsafe conversions |
| C++ named casts | Explicit intent | Slightly longer syntax |
Practical rule of thumb
- Prefer
static_castfor ordinary explicit conversions. - Use
dynamic_castwhen type safety depends on the object's real runtime type. - Avoid C-style casts in modern C++ code.
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
- Downcasting with
static_castwhen the real type is uncertain - Frequent C-style casts in C++ code
- Using
dynamic_caston a non-polymorphic base class - Repeated casts that suggest missing virtual functions
Memory aid
static_cast= trust the type relationshipdynamic_cast= verify the type at runtime- C-style cast = unclear shortcut, avoid when possible
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++?
Not all casts, but unnecessary casts should be avoided. Many conversions are fine, especially with static_cast. Unsafe or frequent downcasts may indicate a design issue.
What happens if dynamic_cast fails?
For pointers, it returns nullptr. For references, it throws std::bad_cast.
Which cast should I prefer in modern C++?
Usually static_cast for normal explicit conversions, dynamic_cast for safe runtime-checked hierarchy conversions, and avoid C-style casts unless there is a very specific reason.
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
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
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++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.