9

dynamic_casts are slower, but they are safer than static_casts (when used with object hierarchies, of course). My question is, after I've ensured in my debug code that all (dynamic) casts are correct, is there any reason for me not to change them to static_casts?

I plan on doing this with the following construct. (Btw, can you think of a better name than assert_cast? Maybe debug_cast?)

#if defined(NDEBUG)

    template<typename T, typename U>
    T assert_cast(U other) {
        return static_cast<T>(other);
    }

#else

    template<typename T, typename U>
    T assert_cast(U other) {
        return dynamic_cast<T>(other);
    }

#endif

Edit: Boost already has something for this: polymorphic_downcast. Thanks to PlasmaHH for pointing that out.

Paul Manta
  • 30,618
  • 31
  • 128
  • 208
  • 5
    Why do people remove assertions from release code ? Assertions are the best possible tool for debugging delivered code at the client's. – Alexandre C. Sep 09 '11 at 09:36
  • 2
    @Alexandre: for performance? :) – Karoly Horvath Sep 09 '11 at 09:38
  • 3
    @Alexandre: because they don't want their applications to crash! That's bad! It's much better to just trod along stepping over whatever memory or files on disk happen to be on your way. More seriously, you don't want *all* assertions on release code. If you do, you're not using enough assertions. Those you want in the release code, you turn into explicit precondition checks that throw exceptions. – R. Martinho Fernandes Sep 09 '11 at 09:39
  • 3
    @Alexandre I think it's assumed that by the time the program gets to the client, assert failures should have been solved so they are removed to get rid of the overhead. – Paul Manta Sep 09 '11 at 09:39
  • @Alexandre There's no assert being removed from the code. The definition of the assert is being changed from dynamic to static, but no code is actually being removed here. – Kevin Lacquement Sep 09 '11 at 09:39
  • @lacqui I guess he's referring to the `NDEBUG` which would also remove asserts. – Paul Manta Sep 09 '11 at 09:40
  • 1
    In debug mode, shouldn't `assert_cast` for pointer types do something if the output is null but the input is non-null (abort or throw an exception)? Otherwise it's not actually asserting anything. – Steve Jessop Sep 09 '11 at 09:46
  • 1
    @Alexandre C: asserts aren't the best possible tool for client-side debugging. Adding comprehensive logs of every action taken by the program is better. But they're even slower and bloat the binary even bigger than keeping all the asserts in, so pick your point on the sliding scale. Someone else will pick a different point. – Steve Jessop Sep 09 '11 at 09:48
  • Just a wild idea (off topic). Can you shrink your `assert` code to this: `#if defined(NDEBUG) #define dynamic_cast static_cast #endif`. Isn't it very simple ? :) – iammilind Sep 09 '11 at 09:59
  • 1
    even boost has a polymorphic cast that behaves in a similar way, so it cant be that bad ;) – PlasmaHH Sep 09 '11 at 10:00
  • @PlasmaHH Well, then there's no reason to use my own cast. :) – Paul Manta Sep 09 '11 at 10:13
  • @iammilind: not in general, since `dynamic_cast` is sometimes used for what it's designed to do: find out whether or not the cast is possible, and do different things according to null or non-null return, with neither one of them being an error. Replacing all dynamic_cast with static_cast would break that fairly badly. That said, the fact that the questioner is describing dynamic_casts that could be static_casts as "correct" suggests that he never uses it that way... – Steve Jessop Sep 09 '11 at 10:55
  • @R. Martinho: it is better to crash as soon as possible with some information for the developer. – Alexandre C. Sep 09 '11 at 10:57
  • @Steve: failed assertions are logged in my world. – Alexandre C. Sep 09 '11 at 10:58
  • 1
    @Alexandre C.: sure, but do you log all the successful things that lead up to the failure? If so, then you have more context what the client did leading up to failure. This is better than logging nothing until stuff has already started failing. "comprehensive logs of every action taken by the program" > "logs of assert failures". – Steve Jessop Sep 09 '11 at 11:00
  • @Paul: I wonder whether you'd like to revisit this question given the evolution of votes since 9th Sept. – Lightness Races in Orbit Sep 20 '11 at 12:28

5 Answers5

4

No! dynamic_cast does more than just casting. It can check the runtime type of the object. But it can also traverse hierarchies that are unknown to the compiler, but are only known in runtime. static_cast cannot do that.

For example:

class A1
{ 
    virtual ~A1() {} 
};
class A2
{
    virtual ~A2() {} 
};

class B : public A1, public A2
{ };

A1 *a1 = new B;
A2 *a2 = dynamic_cast<A2*>(a1); //no static_cast!

A1 *x = ...;
if (B *b = dynamic_cast<B*>(x)) //no static_cast!
  /*...*/; 
rodrigo
  • 94,151
  • 12
  • 143
  • 190
2

You should assert that the dynamic_cast succeeded:

template<typename T, typename U>
T *assert_cast(U *other) {
    T *t = dynamic_cast<T>(other);
    assert(t);
    return t;
}

Replacing dynamic_cast with static_cast in situation when you are sure that they are equivalent is the same as removing null checks for pointer that you are sure is always non-null. You can do that for performance reasons.

Juraj Blaho
  • 13,301
  • 7
  • 50
  • 96
  • It is best to avoid casts - of any ilk. You can usually work around nor having them at all. – Ed Heal Sep 09 '11 at 09:46
  • I know they are not, but after I've ensured that I haven't done any incorrect casts, is there any reason not to change them to `static_cast`s, to remove the performance overhead? – Paul Manta Sep 09 '11 at 09:46
2

As long as you've tested with every possible combination of runtime factors/variables/inputs, sure. As mentioned in the comments, this would be akin to removing assertions.

There's nothing in the language that would make this inherently unsafe, given the requisite assurances that your casts will always be correct. It does feel inherently unsafe, though, in that you could probably never make such a guarantee.


Update

Konstantin has proved that when dealing with multiple inheritance this technique will only work for single steps up/down the inheritance tree1.

struct A1 { virtual ~A1() {} };
struct A2 { virtual ~A2() {} };
struct A3 { virtual ~A3() {} };

struct B : A1, A2 {};
struct C : A1, A3, A2 {};

int main() {
    A1* a1 = (rand() < RAND_MAX / 2 ? (A1*)new B : (A1*)new C);

    A2* p1 = dynamic_cast<A2*>(a1);
    // ^ succeeds, but is a cross-cast

    // A2* p2 = static_cast<A2*>(a1);
    // ^ ill-formed

    A2* p3 = static_cast<A2*>(static_cast<B*>(a1));
    // ^ must chain, instead.
    // but p3 is invalid because we never
    //   checked that `dynamic_cast<B*>(a1)` is valid.. and it's not

    // Instead, let's expand the `dynamic_cast`s into a chain, too:
    A2* p3 = dynamic_cast<B*>(a1);
    A2* p4 = dynamic_cast<A2>*(a1);
    // ^ p3 fails, so we know that we cannot use `static_cast` here
}

So, you can replace your dynamic_casts with static_casts iff:

  • Each dynamic_cast performs only a single step up or down;
  • Each dynamic_cast is known to always succeed.

1 Actually this is a bit of a simplification as, for example, downcasts will work for any number of steps. But it makes a good rule of thumb.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Yeah, sounds like one of those things you should use only if you're 100 percent sure. As a rule of thumb, don't use this type of assert if user input is involved? – Paul Manta Sep 09 '11 at 09:48
  • @Paul: Indeed. It's more risky than removing assertions, IMO. And yea, I'd go with that rule of thumb too (so that's pretty much every application, then). – Lightness Races in Orbit Sep 09 '11 at 09:49
1

I guess it depends on project. If it is nuclear power station management software then I would prefer safety, if it is 3D game I would prefer performance. You can never be sure that all dynamic_cast will be correct in production code. If performance is more important than safety then remove.

ks1322
  • 33,961
  • 14
  • 109
  • 164
1

after I've ensured in my debug code that all (dynamic) casts are correct, is there any reason for me not to change them to static_casts?

IMHO, If you are 100% sure that all dynamic_cast<> are correct, then there is no reason for not changing them to static_cast<>. You can change them.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 1
    Right, unless you are doing cross-casting, like my answer below. They are correct with `dynamic_cast` but not with `static_cast`. If you only cast down or up, you should be safe. – rodrigo Sep 09 '11 at 10:18
  • @rodrigo, OP has mentioned that he is 100% sure for the safety of all `dynamic_cast<>`s. Based on your comment, someone has downvoted! – iammilind Sep 09 '11 at 10:39
  • 1
    dynamic_cast is not a synonim for a downcast, even if all dynamic_casts are correct it doesn't mean that static_cast would even compile. Also even not all downcasts will work when dynamic_cast is replaced by static_cast: in case of dynamic_cast you'd get a pointer to the most derived object which is quite different from static_cast behaviour. – Konstantin Oznobihin Sep 09 '11 at 10:39
  • 1
    @iammilind: you could be 100% sure about the safety of cross casts you have in your code, but doesn't mean you can use static_casts for them. – Konstantin Oznobihin Sep 09 '11 at 10:41
  • @iammilind. Sorry for the downvote (that wasn't me!), but... cross-casting _is_ safe with`dynamic_cast`ing, but not with `static_cast`. The OP doesn't say that he is not doing it. I think that his question is precisely about this kind of cases. – rodrigo Sep 09 '11 at 10:43
  • 1
    I now understand what @Konstantin is saying, and that does add a new constraint to this technique. Thus this answer is not correct. – Lightness Races in Orbit Sep 09 '11 at 14:45
  • dynamic_casts aren't "correct" or "incorrect", they either succeed or fail, but that doesn't make them correct or incorrect. If you're using them as runtime type assertions, then perhaps they can be changed, but that's not the only use of a dynamic_cast. – xaxxon Aug 09 '16 at 08:37
  • @xaxxon, here "correct" or "incorrect" `dynamic_cast` means whether they compile or not. `dynamic_cast` will Not compile unless the converted type is polymorphic, accessible (`public`, `protected`, `private` etc.) & inherited. – iammilind Aug 09 '16 at 09:10
  • @iammilind static casts don't work on random types, either: !!error: static_cast from 'Random *' to 'Base *', which are not related by inheritance, is not allowed https://godbolt.org/g/mtY7mX – xaxxon Aug 10 '16 at 02:49