Concepts / Understanding dynamic_cast

Understanding dynamic_cast

dynamic_cast to a reference type is used when a different type is unacceptable.

  • Programming

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?

  • The cast succeeds and the code that uses the reference runs

  • std::bad_cast is thrown and the code that uses the reference never runs

  • The reference is bound to a default Smiley and the code runs anyway

Reveal answer
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.

pspointer, declared typeShapeObject at psactual type decided atruntimeSmiley objectcast to Smiley& succeedsOther shape objectcast to Smiley& throwsstd::bad_castpoints tomay bemay be
The pointer ps has the declared type Shape, but the object it points to can have a different actual type, and only that actual type decides whether the reference cast to Smiley succeeds.

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_cast marks 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.

Object returned byread_shapeps points to theobjectIs the object aSmiley?dynamic_cast<Smiley&>(*ps)Bind r to the objectRun code that uses rnormal pathThrow std::bad_castCode that uses rskippednever reachedcatch std::bad_casthandleronly place the mismatch ishandledcast appliedyescontinuenoskippedcontrol jumps
The cast-and-use sequence splits at the type check. The yes branch binds r and runs the code that uses it; the no branch throws std::bad_cast, skips that code, and jumps to the handler.

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.

cpp
Output
Cast failed: not a Smiley
Program continues
Notes on the code
  • read_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 like

    The cast sits inside try, but r.smile() is written after the closing brace.

    Why it fails

    When the cast fails, control skips to the handler, but the later statement still runs and touches a reference that was never bound.

    Do this instead

    Keep 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 like

    Reading the handler as the place where the normal result is produced.

    Why it fails

    The handler runs only when std::bad_cast is thrown. On success the try block completes normally.

    Do this instead

    Treat 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 like

    Writing dynamic_cast<Smiley&>(ps) when the operand should be the object itself.

    Why it fails

    The source's pattern dereferences the pointer, so the operand is the object rather than the pointer.

    Do this instead

    Write 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

MEDIUM

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_cast to 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_cast is 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_cast in a catch block.

Related concepts

More concepts starting with U