Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use dynamic_cast whenever for downcast?

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?

like image 623
Midori Yakumo Avatar asked Sep 27 '19 07:09

Midori Yakumo


People also ask

Should I use dynamic_cast?

This cast is used for handling polymorphism. You only need to use it when you're casting to a derived class. This is exclusively to be used in inheritence when you cast from base class to derived class.

Is Static_cast faster than dynamic_cast?

While typeid + static_cast is faster than dynamic_cast , not having to switch on the runtime type of the object is faster than any of them.

Does dynamic cast require RTTI?

The fact, that the cast is safe is known by the compiler at compile time. This means, that dynamic_cast does not need to use RTTI. We can say in general, that dynamic_cast is a tool for moving around the inheritance tree – up and down.

Is dynamic_cast fast?

dynamic_cast runs at about 14.4953 nanoseconds. Checking a virtual method and static_cast ing runs at about twice the speed, 6.55936 nanoseconds.


2 Answers

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.

like image 137
ALX23z Avatar answered Oct 10 '22 16:10

ALX23z


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.

like image 39
Daniel Langr Avatar answered Oct 10 '22 17:10

Daniel Langr