Should I use dynamic_cast whenever for downcast?
Asked Answered
S

2

5

I noticed that compiler will optimize out some dynamic_cast when the following operation is non-polymorphic, for example the following code:


#include <iostream>

using namespace std;

struct A
{
    virtual ~A() = default;
    virtual int f()
    {
        return 1;
    };
    virtual int g() = 0;
};

struct B : A
{
    int g() const
    {
        return 2;
    }
    int g() override
    {
        return 3;
    }
    int h()
    {
        return 4;
    }
};

int main()
{
    A*   p  = new B();
    auto u  = p->f();
    auto v1 = static_cast<B*>(p)->f();
    auto v2 = dynamic_cast<B*>(p)->f();
    auto w  = p->g();
    auto x1 = static_cast<const B*>(p)->g();
    auto x2 = dynamic_cast<B*>(p)->g();
    auto x3 = dynamic_cast<const B*>(p)->g();
    auto y  = dynamic_cast<B*>(p)->h();
    cout << u << v1 << v2 << w << x1 << x2 << x3 << y << endl;
    delete p;
    return 0;
}

There are only two dynamic_cast calls compiled with g++ -O2, that means that equals to static_cast, so should I always use dynamic_cast for downcast for no extra overhead to be considered?

Souse answered 27/9, 2019 at 7:18 Comment(2)
Artificial code is pretty dangerous. It is just as obvious to the optimizer that p is actually a B* as it is to programmer's eyes. In real code that is almost never the case, say an A* function argument. In which case you'd definitely prefer the dynamic cast if you don't otherwise have final guarantees. If the function gets inlined then the optimizer can be smart again.Sudhir
@HansPassant: in what way would the compiler compile the code differently, if p was a function parameter? As I see, the compiler should have not emit any dynamic_cast for OP's code, and neither for p-as-a-parameter version.Definition
M
3

In fact, I don't see any effective difference in using static_cast and dynamic_cast here. First, if you call a virtual function via a casted pointer, all the following calls will have the very same effect — they will trigger dynamic dispatching:

auto v0 = p->f();
auto v1 = static_cast<B*>(p)->f();
auto v2 = dynamic_cast<B*>(p)->f();

dynamic_cast might add some overhead, but the observable effect will be the same. Namely. B::f will be called (if it's overridden).

As for non-virtual functions from a derived class, you need to cast:

auto x1 = static_cast<const B*>(p)->g();
auto x3 = dynamic_cast<const B*>(p)->g();

If those casts are invalid, you will get undefined behavior in both cases. If they are valid, there will be effectively the same. Just again, with some additional overhead of dynamic_cast.

Note that, IMO, there is no dynamic_cast optimized away in your program. You can observe two dynamic_cast calls in your assembly, and you have two different forms of dynamic_cast in your code. I assume a compiler is doing dynamic_cast<B*> only once and uses the result 3 times.


Note that you can bypass the dynamic dispatch by manually selecting which f you want to call by the following syntax:

auto y1 = static_cast<B*>(p)->A::f();
auto y2 = static_cast<B*>(p)->B::f();

Or, with dynamic_cast the very same way.

Live demo: https://wandbox.org/permlink/CZRLPWxHjSMk8dFK.

Marquet answered 27/9, 2019 at 8:47 Comment(3)
Agree, it feels that OP is not aware how to use virtual functions properly. Though, on technical note dynamic_cast<B*>(p) and then calling f() is different from simply calling f() in case p is a base of B. Say, you want to call f() only in the case when p is actually B or derives from it - and return an error or false otherwise.Caseous
so should I use static_cast or dynamic_cast "for non-virtual functions from a derived class" ?Souse
@MidoriYakumo It depends on whether you prefer performance over safety. If you are 100% sure that p points to an object of type B and this condition will be preserved within future modifications of the code, then you can use static_cast. Otherwise use dynamic_cast and check the result.Marquet
C
6

The main issue with dynamic_cast is that they are very slow and complicated. Compilers can indeed optimize it when they are aware of the actual type in compile time but not always. Technically, your code should have 0 dynamic casts if compilers knew how to do it properly. So don't trust too much on the exact mechanisms of optimization that compilers use.

Certain parts of your code could've been optimized via abuse of undefined behavior. For example:

dynamic_cast<B*>(p)->f(); 
// this is optimized instantly to p->f(); 
// if dynamic_cast returns nullptr it would be undefined behavior IIRC, 
// so optimizer can assume that the cast is successful and it becomes equivalent to
// static_cast<B*>(p)->f() after optimization,
// regardless of whether p is actually of type B or not

In general, it is difficult to provide concrete methodology on dynamic casts, if performance allows it, always dynamic cast on down casting. Not doing so is possible undefined behavior and security leak.

If you have no guarantee that the derived class is of this specific type - you have no choice but to use dynamic cast.

If you have a guarantee but there might be bugs in the code, consider making a static assert with dynamic cast inside, and using static cast in optimized versions.

Caseous answered 27/9, 2019 at 7:46 Comment(3)
So that's why they say: use static_cast for release and dynamic_cast with checking for develop?Souse
@MidoriYakumo I would say: do not use casts at all if you don't need to. Anyway, neither static_cast nor dynamic_cast will change the dynamic type of the object. If you call a virtual function on a casted pointer, the virtual function call mechanism will be still involved.Marquet
@MidoriYakumo depends. In certain situations it is impossible to avoid usage of dynamic casts - say you make a library and users of the library add extra classes. Also, if it is used rarely (say, on very high level functions), there is little need to make complicated solutions and simply use dynamic cast for better safety and clarity. As Daniel Langr mentioned, there is no need to make dynamic cast when you simply call a virtual function. By the way, in a recent talk Herb Sutter suggested adding down_cast - a quick safe option of dynamic_cast that works only in simple but most frequent cases.Caseous
M
3

In fact, I don't see any effective difference in using static_cast and dynamic_cast here. First, if you call a virtual function via a casted pointer, all the following calls will have the very same effect — they will trigger dynamic dispatching:

auto v0 = p->f();
auto v1 = static_cast<B*>(p)->f();
auto v2 = dynamic_cast<B*>(p)->f();

dynamic_cast might add some overhead, but the observable effect will be the same. Namely. B::f will be called (if it's overridden).

As for non-virtual functions from a derived class, you need to cast:

auto x1 = static_cast<const B*>(p)->g();
auto x3 = dynamic_cast<const B*>(p)->g();

If those casts are invalid, you will get undefined behavior in both cases. If they are valid, there will be effectively the same. Just again, with some additional overhead of dynamic_cast.

Note that, IMO, there is no dynamic_cast optimized away in your program. You can observe two dynamic_cast calls in your assembly, and you have two different forms of dynamic_cast in your code. I assume a compiler is doing dynamic_cast<B*> only once and uses the result 3 times.


Note that you can bypass the dynamic dispatch by manually selecting which f you want to call by the following syntax:

auto y1 = static_cast<B*>(p)->A::f();
auto y2 = static_cast<B*>(p)->B::f();

Or, with dynamic_cast the very same way.

Live demo: https://wandbox.org/permlink/CZRLPWxHjSMk8dFK.

Marquet answered 27/9, 2019 at 8:47 Comment(3)
Agree, it feels that OP is not aware how to use virtual functions properly. Though, on technical note dynamic_cast<B*>(p) and then calling f() is different from simply calling f() in case p is a base of B. Say, you want to call f() only in the case when p is actually B or derives from it - and return an error or false otherwise.Caseous
so should I use static_cast or dynamic_cast "for non-virtual functions from a derived class" ?Souse
@MidoriYakumo It depends on whether you prefer performance over safety. If you are 100% sure that p points to an object of type B and this condition will be preserved within future modifications of the code, then you can use static_cast. Otherwise use dynamic_cast and check the result.Marquet

© 2022 - 2024 — McMap. All rights reserved.