views:

1343

answers:

4

I saw one book on C++ mentioning that navigating inheritance hierarchies using static cast is more efficient than using dynamic cast.

Example:

#include <iostream>
#include <typeinfo>

using namespace std;

class Shape { public: virtual ~Shape() {}; };
class Circle : public Shape {};
class Square : public Shape {};
class Other {};

int main() {
    Circle c;

    Shape* s = &c; // Upcast: normal and OK

    // More explicit but unnecessary:
    s = static_cast<Shape*>(&c);
    // (Since upcasting is such a safe and common
    // operation, the cast becomes cluttering)

    Circle* cp = 0;
    Square* sp = 0;

    // Static Navigation of class hierarchies
    // requires extra type information:
    if(typeid(s) == typeid(cp)) // C++ RTTI
        cp = static_cast<Circle*>(s);
    if(typeid(s) == typeid(sp))
        sp = static_cast<Square*>(s);
    if(cp != 0)
        cout << "It's a circle!" << endl;
    if(sp != 0)
        cout << "It's a square!" << endl;

    // Static navigation is ONLY an efficiency hack;
    // dynamic_cast is always safer. However:
    // Other* op = static_cast<Other*>(s);
    // Conveniently gives an error message, while
    Other* op2 = (Other*)s;
    // does not
} ///:~

However, both dynamic cast and static cast (as implemented above) need RTTI enabled for such navigation to work. It's just that dynamic cast requires the class hierarchy to be polymorphic (i.e. base class having at least one virtual function).
Where does this efficiency gain for static cast come from? The book does mention that dynamic cast is the preferred way for doing type-safe downcasting.

A: 

dynamic_cast would return NULL if you hadn't done the typeid check and the cast couldn't succeed. static_cast would succeed (and lead to undefined behavior, such as an eventual crash). That's likely the speed difference.

wrang-wrang
bad_cast exception will be thrown incase we use references with dynamic_cast. With pointer variable, NULL would be returned for an incorrect attempt.
Ankur
Cool, thanks for the clarification. I'll update to reflect it.
wrang-wrang
+6  A: 

It's much better to avoid switching on types at all if possible. This is usually done by moving the relevant code to a virtual method that is implemented differently for different subtypes:

class Shape {
public:
    virtual ~Shape() {};
    virtual void announce() = 0;  // And likewise redeclare in Circle and Square.
};

void Circle::announce() {
    cout << "It's a circle!" << endl;
}

void Square::announce() {
    cout << "It's a square!" << endl;
}

// Later...
s->announce();

If you are working with a pre-existing inheritance hierarchy that you can't change, investigate the Visitor pattern for a more extensible alternative to type-switching.

More info: static_cast does not require RTTI, but a downcast using it can be unsafe, leading to undefined behaviour (e.g. crashing). dynamic_cast is safe but slow, because it checks (and therefore requires) RTTI info. The old C-style cast is even more unsafe than static_cast because it will quietly cast across completely unrelated types, where static_cast would object with a compile-time error.

j_random_hacker
While this is a good advice in general, it is not the answer Ankur is looking for, as it does not explain the difference between the two casts.
Franci Penov
I suppose you're right -- he basically asked "What is the most efficient way to point the gun at my feet?", and I foolishly answered with "Don't aim the gun at your feet", which is not relevant. :-P
j_random_hacker
Yes, nonetheless good tip.
Ankur
Exactly. Before you answer, did you considered that maybe his goal is to learn the proper gun-safety practices? A wise decision, if you ask me, especially if one wants to work at the shooting range...
Franci Penov
+1 The best advice on the performance of either way of downcasting is don't worry. Avoid downcasting. If you keep that maxim, at the end the few places where you actually need to use type switching will be so few that performance there will not be an issue.
David Rodríguez - dribeas
@Franci: Point taken. I guess I inferred that the OP was "heading down the wrong track" because he was concerned about speed -- that suggests to me that he's considering using one of these approaches extensively, rather than on just the few unusual situations where it might be justified.
j_random_hacker
+6  A: 

static_cast per se DOESN'T need RTTI -- typeid does (as does dynamic_cast), but that's a completely different issue. Most casts are just telling the compiler "trust me, I know what I'm doing" -- dynamic_cast is the exception, it asks the compiler to check at runtime and possibly fail. That's the big performance difference right there!

Alex Martelli
Do you see any performance difference with dynamic_cast and static_cast as implemented above (it uses typeid).
Ankur
@ankur, the use of `typeid` in the code above is very specific -- it checks for exactly 2 types. If there was any further subclassing the typeid check would fail. `dynamic_cast` is MUCH more general (especially as it needs to deal with MULTIPLE inheritance too, whether you're using it anywhere or not!), and it WOULD work fine even if further subclassing happened, so it's hardly surprising that it may be slower!
Alex Martelli
Got your point. Thanks.
Ankur
+2  A: 

With the static cast (and typeid check) you cannot downcast to an intermediate type (child derives from father derives from grandfather, you cannot downcast from grandfather to father) the usage is a little more limited. static_cast without the typeid check is sacrificing correctness for perfomance, and then you know what they say:

He who sacrifices correctness for performance deserves neither

Then of course, there are situations where you are in desperate need of a few CPU instructions and there is nowhere else to look for improvements and you are actually safe on what you are doing and you have meassured (right?) that the only place to gain performance is using static_cast instead of dynamic_cast... then you know you must rework your design, or your algorithms or get better hardware.

The restrictions you impose by using rtti + static_cast is that you will not be able to extend your code with new derived classes at a later time without reworking all places where you have used this trick to gain just a few CPU instructions. That reworking itself will probably take more time (engineering time that is more expensive) than the CPU time you have obtained. If, at any rate, the time devoted to downcasts is noticeable, then rework your design as j_random_hacker suggests, it will improve both in design and performance.

David Rodríguez - dribeas