polymorphic_downcast


Header: "boost/cast.hpp"

Sometimes dynamic_cast is considered too inefficient (measured, I'm sure!). There is runtime overhead for performing dynamic_casts. To avoid that overhead, it is tempting to use static_cast, which doesn't have such performance implications. static_cast for downcasts can be dangerous and cause errors, but it is faster than dynamic_cast. If the extra speed is required, we must make sure that the downcasts are safe. Whereas dynamic_cast tests the downcasts and returns the null pointer or throws an exception on failure, static_cast just performs the necessary pointer arithmetic and leaves it up to the programmer to make sure that the conversion is valid. To be sure that static_cast is safe for downcasting, you must make sure to test every conversion that will be performed. polymorphic_downcast tests the cast with dynamic_cast, but only in debug builds; it then uses static_cast to perform the conversion. In release mode, only the static_cast is performed. The nature of the cast implies that you know it can't possibly fail, so there is no error handling, and no exception is ever thrown. So what happens if a polymorphic_downcast fails in a non-debug build? Undefined behavior. Your computer may melt. The Earth may stop spinning. You may float above the clouds. The only thing you can safely assume is that bad things will happen to your program. If a polymorphic_downcast fails in a debug build, it asserts on the null pointer result of dynamic_cast.

Before considering how to speed up a program by exchanging polymorphic_downcast for dynamic_cast, review the design. Optimizations on casts are likely indicators of a design problem. If the downcasts are indeed needed and proven to be performance bottlenecks, polymorphic_downcast is what you need. You can only find erroneous casts in testing, not production (release builds), and if you've ever had to listen to a screaming customer on the other end of the phone, you know that catching errors in testing is rather important and makes life a lot easier. Even more likely is that you've been the customer from time to time, and know firsthand how annoying it is to find and report someone else's problems. So, use polymorphic_downcast if needed, but tread carefully.

Usage

polymorphic_downcast is used in situations where you'd normally use dynamic_cast but don't because you're sure which conversions will take place, that they will all succeed, and that you need the improved performance it brings. Nota bene: Be sure to test all possible combinations of types and casts using polymorphic_downcast. If that's not possible, do not use polymorphic_downcast; use dynamic_cast instead. When you decide to go ahead and use polymorphic_downcast, include "boost/cast.hpp".

 #include <iostream> #include "boost/cast.hpp" struct base {   virtual ~base() {}; }; struct derived1 : public base {   void foo() {     std::cout << "derived1::foo()\n";   } }; struct derived2 : public base {   void foo() {     std::cout << "derived2::foo()\n";   } }; void older(base* p) {   // Logic that suggests that p points to derived1 omitted   derived1* pd=static_cast<derived1*>(p);   pd->foo(); // <-- What will happen here? } void newer(base* p) {   // Logic that suggests that p points to derived1 omitted   derived1* pd=boost::polymorphic_downcast<derived1*>(p);   // ^-- The above cast will cause an assertion in debug builds   pd->foo(); } int main() {        derived2* p=new derived2;        older(p); // <-- Undefined        newer(p); // <-- Well defined in debug build } 

The static_cast in the function older will succeed,[6] and as bad luck would have it, the existence of a member function foo lets the error (probably, but again, no guarantees hold here) slip until someone with an error report in one hand and a debugger in the other starts looking into some strange behavior. When the pointer is downcast using static_cast to a derived1*, the compiler has no option but to trust the programmer that the conversion is valid. However, the pointer passed to older is in fact pointing to an instance of derived2. Thus, the pointer pd in older actually points to a completely different type, which means that anything can happen. That's the risk one takes when using a static_cast to downcast. The conversion will always "succeed" but the pointer may not be valid.

[6] At least it will compile.

In the call to function newer, the "better static_cast," polymorphic_downcast not only catches the error, it is also kind enough to pinpoint the location of the error by asserting. Of course, that's true only for debug builds, where the cast is tested by a dynamic_cast. Letting an invalid conversion through to release will cause grief. In other words, you get added safety for debug builds, but that doesn't necessarily mean that you've tried all possible conversions.

Summary

Performing downcasts using static_cast is dangerous in many situations. You should almost never do it, but if the need does arise, some additional safety can be bought by using polymorphic_downcast. It adds tests in debug builds, which can help find conversion errors, but you must test all possible conversions to make its use safe.

  • If you are downcasting and need the speed of static_cast in release builds, use polymorphic_downcast; at least you'll get assertions for errors during testing.

  • If it's not possible to cover all possible casts in testing, do not use polymorphic_downcast.

Remember that this is an optimization, and you should only apply optimizations after profiling demonstrates the need for them.



    Beyond the C++ Standard Library(c) An Introduction to Boost
    Beyond the C++ Standard Library: An Introduction to Boost
    ISBN: 0321133544
    EAN: 2147483647
    Year: 2006
    Pages: 125

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net