dynamic_cast Failure on Reference Types in C++
In C++, dynamic_cast is used for safely converting
pointers and references up, down, and across an inheritance hierarchy at
runtime using Run-Time Type Information (RTTI). While a failed
dynamic_cast on a pointer safely evaluates to
nullptr, a failed dynamic_cast on a reference
type throws a std::bad_cast exception. This article
explains why this behavioral difference exists, what happens during the
failure, and how to handle it properly in your applications.
Why Reference dynamic_cast Throws an Exception
In the C++ language specification, references must always bind to a valid object; a "null reference" does not exist in standard C++.
When you use dynamic_cast on a pointer, the compiler can
signify failure by returning a null pointer:
Derived* d = dynamic_cast<Derived*>(base_ptr);
if (d == nullptr) {
// Cast failed
}Because a reference cannot represent a null state,
dynamic_cast cannot return a null reference to indicate
that the source object is not of the target type. To enforce type safety
and signal failure, the runtime environment throws a
std::bad_cast exception instead.
The std::bad_cast
Exception
The std::bad_cast class is defined in the
<typeinfo> standard header and inherits from
std::exception. When dynamic casting to a reference type
fails:
- The runtime inspects the dynamic type of the operand using the class's vtable.
- It detects that the referenced object cannot be cast to the target reference type.
- It constructs and throws an instance of
std::bad_cast.
If the exception is not handled with a
try/catch block, std::terminate
is called, resulting in the abnormal termination of the program.
Code Example: Handling a Failed Reference Cast
To safely handle reference-based dynamic_cast
operations, wrap the cast inside a try/catch
block:
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual ~Base() = default; // Polymorphic base required for RTTI
};
class DerivedA : public Base {};
class DerivedB : public Base {};
void process(Base& b) {
try {
DerivedA& a = dynamic_cast<DerivedA&>(b);
std::cout << "Successfully cast to DerivedA\n";
} catch (const std::bad_cast& e) {
std::cerr << "dynamic_cast failed: " << e.what() << '\n';
}
}
int main() {
DerivedB b_obj;
process(b_obj); // Throws std::bad_cast because b_obj is DerivedB, not DerivedA
return 0;
}Key Differences: Pointer Cast vs. Reference Cast
| Feature | Pointer dynamic_cast |
Reference dynamic_cast |
|---|---|---|
| Syntax | dynamic_cast<T*>(ptr) |
dynamic_cast<T&>(ref) |
| Failure Mechanism | Returns nullptr |
Throws std::bad_cast |
| Required Header | None (built-in language feature) | <typeinfo> (for
catching std::bad_cast) |
| Control Flow | Handled with standard if
checks |
Handled with
try/catch blocks |
| Intended Use | When failure is an expected, regular outcome | When failure represents an exceptional condition |
Best Practices
- Use pointer
dynamic_castfor optional interfaces: If an object may or may not implement a specific interface and casting failure is a normal execution path, use pointer casts and check fornullptr. This avoids the runtime overhead associated with exception handling. - Use reference
dynamic_castfor assertions: If an object is contractually expected to be a specific derived type, use reference casting. An unexpected type directly triggers an exception, making it ideal for enforcing invariants.