Search

12.9 — Dynamic casting

Way back in lesson 6.16 -- Explicit type conversion (casting) and static_cast, we examined the concept of casting, and the use of static_cast to convert variables from one type to another.

In this lesson, we’ll continue by examining another type of cast: dynamic_cast.

The need for dynamic_cast

When dealing with polymorphism, you’ll often encounter cases where you have a pointer to a base class, but you want to access some information that exists only in a derived class.

Consider the following (slightly contrived) program:

In this program, function getObject() always returns a Base pointer, but that pointer may be pointing to either a Base or a Derived object. In the case where the pointer is pointing to a Derived object, how would we call Derived::getName()?

One way would be to add a virtual function to Base called getName() (so we could call it with a Base pointer/reference, and have it dynamically resolve to Derived::getName()). But what would this function return if you called it with a Base pointer/reference that was actually pointing to a Base object? There isn’t really any value that makes sense. Furthermore, we would be polluting our Base class with things that really should only be the concern of the Derived class.

We know that C++ will implicitly let you convert a Derived pointer into a Base pointer (in fact, getObject() does just that). This process is sometimes called upcasting. However, what if there was a way to convert a Base pointer back into a Derived pointer? Then we could call Derived::getName() directly using that pointer, and not have to worry about virtual function resolution at all.

dynamic_cast

C++ provides a casting operator named dynamic_cast that can be used for just this purpose. Although dynamic casts have a few different capabilities, by far the most common use for dynamic casting is for converting base-class pointers into derived-class pointers. This process is called downcasting.

Using dynamic_cast works just like static_cast. Here’s our example main() from above, using a dynamic_cast to convert our Base pointer back into a Derived pointer:

This prints:

The name of the Derived is: Apple

dynamic_cast failure

The above example works because b is actually pointing to a Derived object, so converting b into a Derived pointer is successful.

However, we’ve made quite a dangerous assumption: that b is pointing to a Derived object. What if b wasn’t pointing to a Derived object? This is easily tested by changing the argument to getObject() from true to false. In that case, getObject() will return a Base pointer to a Base object. When we try to dynamic_cast that to a Derived, it will fail, because the conversion can’t be made.

If a dynamic_cast fails, the result of the conversion will be a null pointer.

Because we haven’t checked for a null pointer result, we access d->getName(), which will try to dereference a null pointer, leading to undefined behavior (probably a crash).

In order to make this program safe, we need to ensure the result of the dynamic_cast actually succeeded:

Rule

Always ensure your dynamic casts actually succeeded by checking for a null pointer result.

Note that because dynamic_cast does some consistency checking at runtime (to ensure the conversion can be made), use of dynamic_cast does incur a performance penalty.

Also note that there are several cases where downcasting using dynamic_cast will not work:
1) With protected or private inheritance.
2) For classes that do not declare or inherit any virtual functions (and thus don’t have a virtual table).
3) In certain cases involving virtual base classes (see this page for an example of some of these cases, and how to resolve them).

Downcasting with static_cast

It turns out that downcasting can also be done with static_cast. The main difference is that static_cast does no runtime type checking to ensure that what you’re doing makes sense. This makes using static_cast faster, but more dangerous. If you cast a Base* to a Derived*, it will “succeed” even if the Base pointer isn’t pointing to a Derived object. This will result in undefined behavior when you try to access the resulting Derived pointer (that is actually pointing to a Base object).

If you’re absolutely sure that the pointer you’re downcasting will succeed, then using static_cast is acceptable. One way to ensure that you know what type of object you’re pointing to is to use a virtual function. Here’s one (not great because it uses a global variable) way to do that:

But if you’re going to go through all of the trouble to implement this (and pay the cost of calling a virtual function and processing the result), you might as well just use dynamic_cast.

dynamic_cast and references

Although all of the above examples show dynamic casting of pointers (which is more common), dynamic_cast can also be used with references. This works analogously to how dynamic_cast works with pointers.

Because C++ does not have a “null reference”, dynamic_cast can’t return a null reference upon failure. Instead, if the dynamic_cast of a reference fails, an exception of type std::bad_cast is thrown. We talk about exceptions later in this tutorial.

dynamic_cast vs static_cast

New programmers are sometimes confused about when to use static_cast vs dynamic_cast. The answer is quite simple: use static_cast unless you’re downcasting, in which case dynamic_cast is usually a better choice. However, you should also consider avoiding casting altogether and just using virtual functions.

Downcasting vs virtual functions

There are some developers who believe dynamic_cast is evil and indicative of a bad class design. Instead, these programmers say you should use virtual functions.

In general, using a virtual function should be preferred over downcasting. However, there are times when downcasting is the better choice:

  • When you can not modify the base class to add a virtual function (e.g. because the base class is part of the standard library)
  • When you need access to something that is derived-class specific (e.g. an access function that only exists in the derived class)
  • When adding a virtual function to your base class doesn’t make sense (e.g. there is no appropriate value for the base class to return). Using a pure virtual function may be an option here if you don’t need to instantiate the base class.

A warning about dynamic_cast and RTTI

Run-time type information (RTTI) is a feature of C++ that exposes information about an object’s data type at runtime. This capability is leveraged by dynamic_cast. Because RTTI has a pretty significant space performance cost, some compilers allow you to turn RTTI off as an optimization. Needless to say, if you do this, dynamic_cast won’t function correctly.


12.10 -- Printing inherited classes using operator<<
Index
12.8 -- Object slicing

7 comments to 12.9 — Dynamic casting

  • Alek

    thanks a million man,although I haven't read all of his code and dunno why he had created so many functions and etc..they are actually scary looking and I don't have the confidence to try to understand them xd. after reading ur comment several times I can finally understand what you mean.what you mean is in a condition where functions cannot be inlined the static is a little more performant but the extra code isnot worth it.and also a generic question,should I worry about little stuff now ? or I should just leave my questions behind for now and try to be satisfied with the information I learn about here and later go back to learn deeplier?e.g also you have well written about dynamic_cast here I still don't know how it is performed how does it use vtable and why, and a lot of other things about it.
    how did you walk this road yourself ?because I really cannot be satisfied with shallow learning about things.

    • nascardriver

      > should I worry about little stuff now ?
      If the little stuff only requires a minor change to your code, yes. For example ++prefix vs postfix++. In most cases, this is either none or only a minor performance difference, but it's 0 extra effort to use ++prefix, so use ++prefix. On the other hand you have code like the one you posted, where you'd have to replace 1 line with what, 30 lines? Those 30 lines are also a lot harder to maintain and it's a lot easier to make mistakes, so stay away from it until you need to go there.

      > should just leave my questions behind for now and try to be satisfied with the information I learn about here
      Question everything, but postpone it when you notice it's becoming too much. You're likely to learn about a better way or recognize why your idea wouldn't have worked out.

      > you have well written about dynamic_cast here I still don't know how it is performed
      It's not standardized how it is performed, only what it performs. The important part is that it doesn't change any values. A pointer stays a pointer and a reference stays a reference. All it has to do at runtime is to check if the types are compatible, then return the original pointer or a `nullptr` (Or throw).

      The vtable contains information about the type. Taking `A` from my updated version of your code

      Alongside the destructor(s) and the virtual member functions `a_`, `b_`, `c_`, there's a "typeinfo for A". If you have a `Base` pointer that points to an `A`, the vtable is the one of `A`, and in there it says that this object is an `A`.
      You give a `Base*` to `dynamic_cast` and tell it to case to `A*`, the `dynamic_cast` looks into the vtable, sees that `Base*` points to an `A` because of the typeinfo in the vtable, and returns the same pointer back to you (With a different type). If the vtable doesn't say "typeinfo for A", `dynamic_cast` returns a `nullptr`.

      > how did you walk this road yourself ?
      Shallow learning is good for a start, because you'll learn which feature to use in which situation. You won't learn how things work under the hood and I don't think there's a single resource that can teach all this. You'll learn about details by running into problems.
      Why is my code so slow?
      Let me change this thing to that other thing.
      It's faster now, why? I'll look it up and if I don't find it I ask someone.
      Learned about vtables and `dynamic_cast` pow!

  • desgroup

    With virtual functions dynamic_cast has already lost most of its use :)

    The purpose of this benchmark was to benchmark the performance of using dynamic_cast in order to detect the underlying type. I suggested to Alek to use an enum in the base class or std::variant instead.

    The "dynamic proper" benchmark doesn't seem to evaluate the cost of dynamic_cast at all... it just seems to evaluate the cost of virtual functions (to be honest I'm actually a bit impressed that virtual functions are able to perform so closely :O).

    • nascardriver

      > The purpose of this benchmark was to benchmark the performance of using dynamic_cast in order to detect the underlying type
      I understand, the issue is that this isn't a practical use case. You can use a broom's handle to push away dirt, but comparing that to using a vacuum cleaner doesn't make sense, because you're misusing the broom. So I turned the broom around to the way it's meant to be used to get a fair comparison.

      If you need fast type access, a hybrid approach can be used as you suggested. That is, storing the type in the base directly to skip the vtable lookup.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]