Appearance
Dynamic Casting
Overview
dynamic_cast is a runtime type-checking cast that safely converts pointers and references in polymorphic class hierarchies. Unlike other casts, it performs runtime checks to ensure the conversion is valid, making it the safest way to navigate inheritance relationships.
How dynamic_cast Works
dynamic_cast requires:
- Polymorphic base class: The base class must have at least one virtual function
- RTTI enabled: Runtime Type Information must be available (can be disabled with
-fno-rtti) - Valid inheritance relationship: The types must be related through inheritance
cpp
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual ~Base() = default;
virtual void print() const { std::cout << "Base" << std::endl; }
};
class Derived1 : public Base {
public:
void print() const override { std::cout << "Derived1" << std::endl; }
void derived1Method() { std::cout << "Derived1 specific method" << std::endl; }
};
class Derived2 : public Base {
public:
void print() const override { std::cout << "Derived2" << std::endl; }
void derived2Method() { std::cout << "Derived2 specific method" << std::endl; }
};
class Unrelated {};
void demonstrateDynamicCast() {
Derived1 d1;
Base* basePtr = &d1;
// Successful downcast
if (Derived1* d1Ptr = dynamic_cast<Derived1*>(basePtr)) {
d1Ptr->derived1Method(); // Safe to call
std::cout << "Successfully cast to Derived1" << std::endl;
}
// Failed downcast
if (Derived2* d2Ptr = dynamic_cast<Derived2*>(basePtr)) {
d2Ptr->derived2Method();
} else {
std::cout << "Cast to Derived2 failed" << std::endl;
}
// Cross-cast (sibling classes)
Derived1* d1Ptr = &d1;
if (Derived2* d2Ptr = dynamic_cast<Derived2*>(d1Ptr)) {
// This will fail - they're not related
d2Ptr->derived2Method();
} else {
std::cout << "Cross-cast failed" << std::endl;
}
}Pointer vs Reference Casting
dynamic_cast behaves differently with pointers and references:
cpp
void pointerVsReferenceCasting() {
Derived1 d1;
Base* basePtr = &d1;
// Pointer casting - returns nullptr on failure
Derived1* d1Ptr = dynamic_cast<Derived1*>(basePtr);
if (d1Ptr) {
std::cout << "Pointer cast succeeded" << std::endl;
}
Derived2* d2Ptr = dynamic_cast<Derived2*>(basePtr);
if (!d2Ptr) {
std::cout << "Pointer cast failed, got nullptr" << std::endl;
}
// Reference casting - throws std::bad_cast on failure
try {
Derived1& d1Ref = dynamic_cast<Derived1&>(*basePtr);
std::cout << "Reference cast succeeded" << std::endl;
} catch (const std::bad_cast& e) {
std::cout << "Reference cast failed: " << e.what() << std::endl;
}
try {
Derived2& d2Ref = dynamic_cast<Derived2&>(*basePtr);
std::cout << "This won't execute" << std::endl;
} catch (const std::bad_cast& e) {
std::cout << "Reference cast failed as expected: " << e.what() << std::endl;
}
}Multiple Inheritance and Virtual Inheritance
dynamic_cast handles complex inheritance scenarios:
cpp
class A {
public:
virtual ~A() = default;
virtual void aMethod() { std::cout << "A method" << std::endl; }
};
class B {
public:
virtual ~B() = default;
virtual void bMethod() { std::cout << "B method" << std::endl; }
};
class C : public A, public B {
public:
void cMethod() { std::cout << "C method" << std::endl; }
};
void multipleInheritanceCasting() {
C c;
A* aPtr = &c;
B* bPtr = &c;
// Cast from A to B (cross-cast)
if (B* bFromA = dynamic_cast<B*>(aPtr)) {
bFromA->bMethod(); // Works!
}
// Cast from B to A (cross-cast)
if (A* aFromB = dynamic_cast<A*>(bPtr)) {
aFromB->aMethod(); // Works!
}
// Downcast to most derived
if (C* cPtr = dynamic_cast<C*>(aPtr)) {
cPtr->cMethod(); // Safe access to C-specific methods
}
}Performance vs Safety
Performance Impact
- Runtime overhead:
dynamic_castperforms runtime type checking - Memory access: May require accessing vtable and type information
- Cache effects: Can cause cache misses in performance-critical code
Safety Benefits
- Type safety: Prevents invalid casts that could cause crashes
- Null pointer protection: Returns nullptr for failed pointer casts
- Exception safety: Throws
std::bad_castfor failed reference casts
cpp
// Unsafe C-style cast - can cause undefined behavior
void unsafeCasting() {
Derived1 d1;
Base* basePtr = &d1;
// DANGEROUS: No runtime checks
Derived2* d2Ptr = (Derived2*)basePtr;
d2Ptr->derived2Method(); // Undefined behavior!
// SAFE: Runtime validation
if (Derived2* safeD2Ptr = dynamic_cast<Derived2*>(basePtr)) {
safeD2Ptr->derived2Ptr->derived2Method(); // Only executes if valid
}
}Alternatives to dynamic_cast
1. Virtual Functions (Preferred)
cpp
class Base {
public:
virtual ~Base() = default;
virtual void derived1Method() { /* default implementation */ }
virtual void derived2Method() { /* default implementation */ }
};
class Derived1 : public Base {
public:
void derived1Method() override { std::cout << "Derived1 method" << std::endl; }
};
// No casting needed - polymorphic behavior
void useVirtualFunctions(Base* ptr) {
ptr->derived1Method(); // Calls appropriate implementation
}2. Type Tags
cpp
enum class Type { Base, Derived1, Derived2 };
class Base {
public:
virtual ~Base() = default;
virtual Type getType() const = 0;
};
class Derived1 : public Base {
public:
Type getType() const override { return Type::Derived1; }
void derived1Method() { std::cout << "Derived1 method" << std::endl; }
};
void useTypeTags(Base* ptr) {
if (ptr->getType() == Type::Derived1) {
static_cast<Derived1*>(ptr)->derived1Method();
}
}3. Visitor Pattern
cpp
class Visitor {
public:
virtual void visit(Derived1*) = 0;
virtual void visit(Derived2*) = 0;
virtual ~Visitor() = default;
};
class Base {
public:
virtual ~Base() = default;
virtual void accept(Visitor& v) = 0;
};
class Derived1 : public Base {
public:
void accept(Visitor& v) override { v.visit(this); }
void derived1Method() { std::cout << "Derived1 method" << std::endl; }
};When to Use dynamic_cast
Use dynamic_cast when:
- You need runtime type checking for safety
- Working with polymorphic hierarchies
- Implementing type-specific behavior that can't be virtualized
- Debugging and logging type information
Avoid dynamic_cast when:
- Performance is critical (use virtual functions instead)
- You can use compile-time polymorphism
- Working with non-polymorphic classes
- You can redesign to avoid the need for casting
Best Practices
- Always check the result of pointer casts
- Use try-catch for reference casts
- Consider alternatives like virtual functions first
- Profile performance if used in hot paths
- Document assumptions about inheritance relationships
cpp
// Good: Always check pointer cast results
void goodPractice(Base* ptr) {
if (Derived1* d1 = dynamic_cast<Derived1*>(ptr)) {
d1->derived1Method();
} else if (Derived2* d2 = dynamic_cast<Derived2*>(ptr)) {
d2->derived2Method();
} else {
std::cout << "Unknown derived type" << std::endl;
}
}
// Bad: Assuming cast will succeed
void badPractice(Base* ptr) {
Derived1* d1 = dynamic_cast<Derived1*>(ptr);
d1->derived1Method(); // Crashes if cast failed!
}dynamic_cast is a powerful tool for safe polymorphic casting, but it should be used judiciously. Always prefer virtual functions when possible, and reserve dynamic_cast for cases where you truly need runtime type information.
Questions
Q: What is required for dynamic_cast to work?
dynamic_cast requires a polymorphic base class (one with at least one virtual function) and RTTI to be enabled. This allows the runtime to determine the actual type of objects.
Q: What happens when a pointer dynamic_cast fails?
When a pointer dynamic_cast fails, it returns nullptr. This allows for safe checking of the result. Reference dynamic_cast throws std::bad_cast on failure.
Q: Which of the following is the safest alternative to dynamic_cast?
Virtual functions and polymorphism are the safest alternative to dynamic_cast. They provide compile-time type safety and avoid runtime overhead while maintaining the same functionality.
Q: What is a cross-cast in the context of dynamic_cast?
A cross-cast is casting between sibling classes in multiple inheritance scenarios. For example, casting from A* to B* when both A and B are base classes of C.
Q: When should you avoid using dynamic_cast?
A: When debugging type information
Avoid dynamic_cast when performance is critical and alternatives like virtual functions exist. dynamic_cast has runtime overhead and can cause cache misses in performance-sensitive code.