Understanding dynamic_cast
dynamic_cast to a reference type is used when a different type is unacceptable.
Predicting a Failed Cast
What do you think happens?
A pointer ps holds the address of a Square object. The program tries to bind a Smiley reference to that same object with a reference cast, and the code that uses the reference comes after the cast. What happens?
Reveal answer
std::bad_cast is thrown and the code that uses the reference never runs.
The object is not of the expected type, so the reference cast fails. Control leaves the cast through an exception and resumes only at a handler that catches std::bad_cast. Any statement that depends on the reference is skipped.
The rest of this article explains why that outcome happens, how to trace the exact point where it diverges from the expected result, and how to write code that handles it safely. Keep your prediction in mind as you read, because the trace later in the article checks it step by step.
When a Different Type Is Unacceptable
Once a program works with a class hierarchy, it often holds a pointer to a base type but needs the object to behave as a more specific type. A cast is the step that asks the program to treat the object as that more specific type. This article focuses on one form of that step: using dynamic_cast to convert to a reference type, and what the program experiences when the object does not match the requested type.
The source describes the situation in which this form of cast is used: when a different type is unacceptable. The program cannot continue with an object of some other type, so it asks for the specific type directly and treats any mismatch as a failure. The reference cast is therefore not a way to try several types and pick one. It is a demand: the object must be of the requested type, or the program must not proceed with it.
Static Type and Actual Object
Two types are involved whenever a base pointer or reference is cast. The first is the declared type, which is written in the program and fixed by the pointer or reference itself. The second is the actual type of the object that is really there at runtime. In the running example, ps is a pointer to a Shape, but the object it points to is whatever the function read_shape returned. The declared type is always Shape. The actual type can be different objects on different runs.
The cast does not change the object. It asks whether the actual object matches the requested type. That question is why the mismatch causes the cast to fail: a Square object does not satisfy a request to view it as a Smiley, no matter that both are shapes.
Reading the Cast Expression
A reference cast in the running example has the form dynamic_cast<Smiley&>(*ps). Each part has a job. The keyword dynamic_cast requests a checked conversion at runtime. The angle brackets hold the target type, which here is Smiley&: the ampersand means the result is a reference to a Smiley rather than a pointer. The parentheses hold the operand, and the leading star in *ps dereferences the pointer, so the operand is the object itself rather than the pointer.
dynamic_castmarks the conversion as a checked runtime request.- The target type Smiley& is where the expected type appears; the trailing ampersand makes the result a reference.
- The operand *ps is the object the pointer refers to, not the pointer itself.
- The result is bound to a reference named r, which then refers to that object as a Smiley.
Two Paths Through One Cast
Trace the cast as a sequence. Both paths start at the same line, and the only question the program asks is whether the object is of the expected type. The path taken decides whether the code that uses r is reached at all. Follow the flowchart from the top, and notice the one point where the two paths split.
On the yes branch, the binding succeeds and the code that uses r runs normally. On the no branch, the program never reaches that code. Control leaves the cast expression by way of the exception, and it resumes only at a handler that catches std::bad_cast. The failure therefore shows up at the cast line itself: the statements after the cast are the ones that go missing, and the handler's output is the sign that the mismatch happened.
A Guarded Cast in Code
The safest way to use a reference cast is to place both the cast and the code that depends on it inside a try block, and to catch std::bad_cast in a handler. The example below is an original illustration of that structure, built on the source's pattern. The Shape class has a virtual destructor so that it is a polymorphic base type, which is what allows dynamic_cast to examine the actual object.
Cast failed: not a Smiley
Program continuesread_shape(2)returns a Square, so the object is not a Smiley.- The cast and the dependent call
r.smile()both sit inside the try block. - Because the cast throws,
r.smile()never runs and the handler prints its message. - The statement after the try and catch still runs, showing that the handler resumes normal execution after the construct.
Change the argument to read_shape(1) and the same code takes the yes branch. The object is a Smiley, the binding succeeds, and r.smile() runs before the success message. The only difference between the two runs is the actual object, and the code text does not change.
Following Control After a Failure
When the cast fails, the program does not simply carry on past the line with a usable reference. The source makes this explicit: the mismatch is treated as a failure, and the catch block is the only place that handles it. Nothing in the try block runs with a reference that was never successfully bound. If you move the statement r.smile() outside the try block, the program can reach a use of r that was never bound, so the protection is lost.
Mistakes With Reference Casts
Common mistake Placing the code that uses the reference outside the try block
What it looks likeThe cast sits inside try, but r.smile() is written after the closing brace.
Why it failsWhen the cast fails, control skips to the handler, but the later statement still runs and touches a reference that was never bound.
Do this insteadKeep the cast and every statement that depends on r inside the same try block.
Common mistake Expecting a catch block to run when the cast succeeds
What it looks likeReading the handler as the place where the normal result is produced.
Why it failsThe handler runs only when std::
bad_castis thrown. On success the try block completes normally.Do this insteadTreat the try block as the success path and the catch block as the mismatch path.
Common mistake Forgetting the leading star on a pointer operand
What it looks likeWriting dynamic_cast<Smiley&>(ps) when the operand should be the object itself.
Why it failsThe source's pattern dereferences the pointer, so the operand is the object rather than the pointer.
Do this insteadWrite dynamic_cast<Smiley&>(*ps) so the operand is the object that ps points to.
Common mistake Assuming a Shape pointer is always a Smiley because the variable is named ps
Tracing a Failed Cast Yourself
In the guarded example, change read_shape(2) to read_shape(1) and then to read_shape(2) again, and trace each run. For each run, write down whether the cast line succeeds, which line first differs from the expected result, and which output lines appear. Then say what would be printed if r.smile() were moved outside the try block for the failing run.
Hints
- Ask first whether the actual object is a Smiley, not what the pointer's declared type is.
- On a failing run, the first line that does not happen is the statement after the cast that depends on r.
- The handler's message is the signal that the mismatch was caught.
Key Takeaways
dynamic_castto a reference type is the right tool when a different type is unacceptable: the program asks for the specific type directly and treats any mismatch as a failure.- The declared type of a pointer or reference is fixed in the code, but the actual object it refers to decides whether the cast succeeds.
- If the object is not of the expected type, std::
bad_castis thrown, the code that uses the reference is skipped, and control resumes only at a handler. - Place the cast and every statement that depends on the reference inside a try block, and handle std::
bad_castin a catch block.