I l@ve RuBoard |
![]() ![]() |
11.9 Converting Arguments: static_cast or dynamic_cast?All the previous code has performed casting with the safe dynamic_cast. But in the case of dynamic_cast, safety comes at a cost in runtime efficiency. At registration time, you already know that your function or functor will fire for a pair of specific, known types. Through the mechanism it implements, the double dispatcher knows the actual types when an entry in the map is found. It seems a waste, then, to have dynamic_cast check again for correctness when a simple static_cast achieves the same result in much less time. There are, however, two cases in which static_cast may fail and the only cast to rely on is dynamic_cast. The first occurs when you're using virtual inheritance. Consider the following class hierarchy: class Shape { ... }; class Rectangle : virtual public Shape { ... }; class RoundedShape : virtual public Shape { ... }; class RoundedRectangle : public Rectangle, public RoundedShape { ... }; Figure 11.2 displays a graphical representation of the relationships between classes in this hierarchy. Figure 11.2. A diamond-shaped class hierarchy using virtual inheritanceThis may not be a very smart class hierarchy, but one thing about designing class libraries is that you never know what your clients might need to do. There are definitely reasonable situations in which a diamond-shaped class hierarchy is needed, in spite of all its caveats. Consequently, the double dispatchers we defined should work with diamond-shaped class hierarchies. The dispatchers actually work fine as of now. But if you try to replace the dynamic_casts with static_casts, you will get compile-time errors whenever you try to cast a Shape& to any of Rectangle&, RoundedShape&, and RoundedRectangle&. The reason is that virtual inheritance works very differently from plain inheritance. Virtual inheritance provides a means for several derived classes to share the same base class object. The compiler cannot just lay out a derived object in memory by gluing together a base object with whatever the derived class adds. In some implementations of multiple inheritance, each derived object stores a pointer to its base object. When you cast from derived to base, the compiler uses that pointer. But the base object does not store pointers to its derived objects. From a pragmatic viewpoint, all this means that after you cast an object of derived type to a virtual base type, there's no compile-time mechanism for getting back to the derived object. You cannot static_cast from a virtual base to an object of derived type. However, dynamic_cast uses more advanced means to retrieve the relationships between classes and works nicely even in the presence of virtual bases. In a nutshell, you must use dynamic_cast if you have a hierarchy using virtual inheritance. Second, let's analyze the situation with a similar class hierarchy, but one that doesn't use virtual inheritance—only plain multiple inheritance. class Shape { ... }; class Rectangle : public Shape { ... }; class RoundedShape : public Shape { ... }; class RoundedRectangle : public Rectangle, public RoundedShape { ... }; Figure 11.3 shows the resulting inheritance graph. Figure 11.3. A diamond-shaped class hierarchy using nonvirtual inheritanceAlthough the shape of the class hierarchy is the same, the structure of the objects is very different. RoundedRectangle now has two distinct subobjects of type Shape. This means that converting from RoundedRectangle to Shape is now ambiguous: Which Shape do you mean—that in the RoundedShape or that in the Rectangle? Similarly, you cannot even static cast a Shape& to a RoundedRectangle& because the compiler doesn't know which Shape subobject to consider. We're facing trouble again. Consider the following code: RoundedRectangle roundRect; Rectangle& rect = roundRect; // Unambiguous implicit conversion Shape& shape1 = rect; RoundedShape& roundShape = roundRect; // Unambiguous implicit // conversion Shape& shape2 = roundShape; SomeDispatcher d; Shape& someOtherShape = ...; d.Go(shape1, someOtherShape); d.Go(shape2, someOtherShape); Here, it is essential that the dispatcher use dynamic_cast to convert the Shape& to a Rounded Shape&. If you try to register a trampoline for converting a Shape& to a RoundedRectangle&, a compile-time error occurs due to ambiguity. There is no trouble at all if the dispatcher uses dynamic_cast. A dynamic_cast<Rounded Rectangle&> applied to any of the two base Shape subobjects of a RoundedRectangle leads to the correct object. As you can see, nothing beats a dynamic cast. The dynamic_cast operator is designed to reach the right object in a class hierarchy, no matter how intricate its structure is. The conclusion that consolidates these findings is this: You cannot use static_cast with a double dispatcher in a class hierarchy that has multiple occurrences of the same base class, whether or not it is through virtual inheritance. This might give you a strong incentive to use dynamic_cast in all dispatchers. However, there are two supplemental considerations.
The solution that Loki adopts is to make casting a policy—CastingPolicy. (Refer to Chapter 1 for a description of policies.) Here, the policy is a class template with two parameters: the source and the destination type. The only function the policy exposes is a static function called Cast. The following is the DynamicCaster policy class. template <class To, class From> struct DynamicCaster { static To& Cast(From& obj) { return dynamic_cast<To&>(obj); } }; The dispatchers FnDispatcher and FunctorDispatcher use CastingPolicy according to the guidelines described in Chapter 1. Here is the modified FunctorDispatcher class. The changes are shown in bold. template < class BaseLhs, class BaseRhs = BaseLhs, ResultType = void, template <class, class> class CastingPolicy = DynamicCaster > class FunctorDispatcher { ... template <class SomeLhs, class SomeRhs, class Fun> void Add(const Fun& fun) { class Adapter : public FunctorType::Impl { Fun fun_; virtual ResultType operator()(BaseLhs& lhs, BaseRhs& rhs) { return fun_( CastingPolicy<SomeLhs, BaseLhs>::Cast(lhs), CastingPolicy<SomeRhs, BaseRhs>::Cast(rhs)); } ... as before ... }; backEnd_.Add<SomeLhs, SomeRhs>( FunctorType(new Adapter(fun)); } }; Cautiously, the casting policy defaults to DynamicCaster. Finally, you can do a very interesting thing with casting policies. Consider the hierarchy in Figure 11.4. There are two categories of casts within this hierarchy. Some do not involve a diamond-shaped structure, so static_cast is safe. Namely, static_cast suffices for casting a Shape& to a Triangle&. On the other hand, you cannot static_cast a Shape& to Rectangle& and any of its derivatives; you must use dynamic_cast. Figure 11.4. A class hierarchy with a diamond-shaped portionSuppose you define your own casting policy template for this class hierarchy, namely ShapeCast. You can make it default to dynamic_cast. You can then specialize the policy for the special cases: template <class To, class From> struct ShapeCaster { static To& Cast(From& obj) { return dynamic_cast<To&>(obj); } }; template<> class ShapeCaster<Triangle, Shape> { static Triangle& Cast(Shape& obj) { return static_cast<Triangle&>(obj); } }; You now get the best of both worlds—speedy casts whenever you can, and safe casts whenever you must. ![]() |
I l@ve RuBoard |
![]() ![]() |