The operator dynamic_cast is a cast operator that makes a checked conversion between pointer or reference types within a polymorphic class hierarchy. It checks the dynamic type of the object at runtime and succeeds only when the conversion is safe. When the conversion is unsafe, it reports the failure in well defined ways: it returns nullptr for pointer conversion or it throws an exception for reference conversion.
The operator can only be used with polymorphic types: that is, class types with at least one virtual function. In the absence of a virtual function, the compiler lacks runtime information and the program will not compile.
A typical hierarchy for illustration will involve a base class and two derived classes:
#include <iostream>
class Shape
{
public:
virtual void draw() const
{
std::cout << "Drawing a shape\n";
}
// Run this destructor when the object lifetime ends
virtual ~Shape() = default;
};
class Circle : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a circle\n";
}
void radius() const
{
std::cout << "Circle has a radius\n";
}
};
class Square : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a square\n";
}
};
Shape is polymorphic since it declares a virtual function. Both Circle and Square override this function, and Circle additionally has a member function of its own.
When a base pointer might point to a derived object, dynamic_cast will safely recover a derived pointer for us.
int main()
{
Shape* ptr{ new Circle{} };
Circle* c{ dynamic_cast<Circle*>(ptr) };
if (c)
{
c->radius();
}
Square* s{ dynamic_cast<Square*>(ptr) };
if (s)
{
std::cout << "This line is not reached\n";
}
else
{
std::cout << "ptr does not point to a Square\n";
}
// Release the dynamically allocated object
delete ptr;
return 0;
}
The first dynamic_cast queries whether the object being pointed to by the base pointer ptr is actually a Circle. Since the dynamic type is Circle, the cast is safe, and the resulting pointer c is non null. It is therefore safe to call c->radius().
The second dynamic_cast queries whether the same object is a Square.
Its dynamic type does not match, so the dynamic_cast operator will return nullptr. The program tests for this and prints a relevant message. The object is then deleted through the base pointer; a virtual destructor ensures that the appropriate derived destructor is called.
Reference forms of the dynamic_cast operator behave slightly differently when the cast is unsuccessful, and rather than return nullptr, throw an exception of type std::bad_cast. A full program demonstrating both success and failure with references is below.
#include <iostream>
#include <typeinfo>
class Shape
{
public:
virtual void draw() const
{
std::cout << "Drawing a shape\n";
}
virtual ~Shape() = default;
};
class Circle : public Shape
{
public:
// Mark this function as an override of a virtual base function
void draw() const override
{
std::cout << "Drawing a circle\n";
}
void radius() const
{
std::cout << "Circle has a radius\n";
}
};
class Square : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a square\n";
}
};
int main()
{
Circle circle;
Shape& ref{ circle };
try
{
Circle& c{ dynamic_cast<Circle&>(ref) };
c.radius();
}
catch (const std::bad_cast& e)
{
std::cout << "Unexpected failure\n";
}
try
{
Square& s{ dynamic_cast<Square&>(ref) };
std::cout << "This line is not reached\n";
}
catch (const std::bad_cast& e)
{
std::cout << "ref is not a Square\n";
}
return 0;
}
The first dynamic_cast is successful, since the reference being referred to by ref really is a Circle, while the second fails and throws an exception for which a catch handler prints a message of failure.
Thus, dynamic_cast provides a means of querying "Is this object actually of that type?" and when the answer is yes, yielding either a pointer or reference to the derived class, otherwise yielding a null pointer or exception without undefined behaviour. It's the standard way to perform safe down casting within a polymorphic hierarchy.
Exercise: dynamic_castStarter code, hints and solutionOpenClose
Practice: dynamic_cast
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Add pointer dynamic_cast tests from Shape* to Circle* and Square*. Report success/null and contrast that failure behavior with the existing reference casts.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
#include <typeinfo>
class Shape
{
public:
virtual void draw() const
{
std::cout << "Drawing a shape\n";
}
virtual ~Shape() = default;
};
class Circle : public Shape
{
public:
// Mark this function as an override of a virtual base function
void draw() const override
{
std::cout << "Drawing a circle\n";
}
void radius() const
{
std::cout << "Circle has a radius\n";
}
};
class Square : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a square\n";
}
};
int main()
{
Circle circle;
Shape& ref{ circle };
try
{
Circle& c{ dynamic_cast<Circle&>(ref) };
c.radius();
}
catch (const std::bad_cast& e)
{
std::cout << "Unexpected failure\n";
}
try
{
Square& s{ dynamic_cast<Square&>(ref) };
std::cout << "This line is not reached\n";
}
catch (const std::bad_cast& e)
{
std::cout << "ref is not a Square\n";
}
return 0;
}Hints
ref refers to a Circle throughout both try blocks. Changing the reference's declared base type does not turn the object into a Square.
A failed reference dynamic_cast throws std::bad_cast, whereas a failed pointer cast would return nullptr. Follow the matching catch block for the Square& attempt.
Worked solution
ref still denotes a Circle. The added pointer casts succeed for Circle* and return nullptr for Square*. The original reference-cast failure throws bad_cast instead, showing the two failure forms without changing the dynamic object type.
#include <iostream>
#include <typeinfo>
class Shape
{
public:
virtual void draw() const
{
std::cout << "Drawing a shape\n";
}
virtual ~Shape() = default;
};
class Circle : public Shape
{
public:
// Mark this function as an override of a virtual base function
void draw() const override
{
std::cout << "Drawing a circle\n";
}
void radius() const
{
std::cout << "Circle has a radius\n";
}
};
class Square : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a square\n";
}
};
int main()
{
Circle circle;
Shape& ref{ circle };
try
{
Circle& c{ dynamic_cast<Circle&>(ref) };
c.radius();
}
catch (const std::bad_cast& e)
{
std::cout << "Unexpected failure\n";
}
try
{
Square& s{ dynamic_cast<Square&>(ref) };
std::cout << "This line is not reached\n";
}
catch (const std::bad_cast& e)
{
std::cout << "ref is not a Square\n";
}
Shape* base = &circle;
std::cout << "Circle pointer succeeds: " << (dynamic_cast<Circle*>(base) != nullptr) << '\n';
std::cout << "Square pointer is null: " << (dynamic_cast<Square*>(base) == nullptr) << '\n';
return 0;
}
Example output
Circle has a radius
ref is not a Square
Circle pointer succeeds: 1
Square pointer is null: 1
0 comments
No comments yet. Be the first.