13

Can anybody give me a real world example of a case when dynamic_cast is needed and can't be worked around at all? Examples I can think of can generally be worked around with double dispatch.

If the constraint is too strong, an example where dynamic_cast is generally the way to go would also be nice.

I'd like to see real examples instead of "it's normally used to cast between types up and down a type tree".

Russell
  • 3,975
  • 7
  • 37
  • 47
  • 1
    have you looked at this: http://stackoverflow.com/questions/28002/regular-cast-vs-static-cast-vs-dynamic-cast – amit Jul 01 '11 at 20:05
  • @amit: that explains what dynamic_cast does, not an example of when one would need it and couldn't accomplish the same task a different way. – Nicol Bolas Jul 01 '11 at 20:08
  • Yep, the example given was almost exactly the example I had in mind and can be worked around with double dispatch. That's why I needed to ask this question further. Thanks. – Russell Jul 01 '11 at 20:09
  • 1
    @Russel: Why are you trying specifically to avoid `dynamic_cast`? Older compilers implemented it extremely slowly but most everyone nowadays implements it as a simple vtbl pointer comparison. – Billy ONeal Jul 01 '11 at 20:28
  • 2
    @Billy, for the sake of knowledge and to explore the possibilities :) – Russell Jul 01 '11 at 21:19
  • @Billy ONeal: I would be very interested in knowing how it is implemented as a simple VTable pointer comparison when it allows casting in the midst of a hierarchy (and across branches). Do you have a nice description of the data/algo used ? – Matthieu M. Jul 02 '11 at 10:29
  • @Matthieu: The class instance has a pointer which points to the vtbl. Classes of the same type can share the same vtbl. Therefore it's easy to check the type of the class because the pointer for a specific type is known ahead of time. I guess it has to degenerate into a few comparisons if you are casting to a class which is not a leaf... I think that could be worked around by putting extra fields in the vtbl structure but I don't know. (I suppose I should have qualified that with *that's what I've been told* .. I've not verified this myself) – Billy ONeal Jul 02 '11 at 17:10
  • @Matthieu: The source of the information in my comment is here: http://stackoverflow.com/q/3314944/82320 – Billy ONeal Jul 02 '11 at 17:20
  • @Billy: typical instantiations of the VTable involve a pointer to the RTTI information, in which the parent(s) class(es) can be recorded, along with the name of the type, etc... The RTTI itself is well specified in the Itanium ABI, but it's rather dry so I never really read it. – Matthieu M. Jul 03 '11 at 10:42

5 Answers5

6

Double dispatch requires that the types that are interacting have intimate knowledge of each other's innards, as it requires that one class call methods on the other class. dynamic_cast works when you cannot modify the innards of a class, or do not wish to break encapsulation of the classes in question.

That is, double dispatch is invasive on the classes involved, while dynamic_cast works without knowledge of the cast in the classes.

You can also use dynamic_cast if you don't know the target method overload which will be invoked. For an example, see this question I posted yesterday.

Finally, double dispatch does not come without it's own headaches

The base class Shape must know about all the derived classes, resulting in circular dependencies. If you derive a new class from Shape (say Triangle), you must update the interface of Shape and the interface/implementation of all the other derived classes. In some cases this is not even an option: you may not have the source code for Shape, or not be willing or permitted to modify it.

Community
  • 1
  • 1
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • You can totally implement N-ary dispatch (multi-methods) without inheritance hierarchies using a list (to store handlers), a template function (to generate handlers) and a function that casts all operands to find a match. Implementing double dispatch with virtual methods is invasive of the classes involved. – André Caron Jul 01 '11 at 20:37
  • No, it's only in error as it assumes double dispatch is always implemented using virtual functions (i.e. like in the `Shape` example). This is not the only way to implement double dispatch and cannot easily be extended to triple dispatch or N-ary dispatch. By maintaining and external list with "test -> function" mappings, you can effectively implement (almost) headache-free multi-methods. – André Caron Jul 04 '11 at 17:04
1

The constraint "can't be worked around at all" is too strong. Any C++ feature can be emulated in C. All you have to do to work around the feature, so to speak, is to use that C code in C++. For example, MFC, a library originating from the depths of time before the 1998 language standardization, offered and still offers its own kind of dynamic cast.

One example where you generally need dynamic casting is the visitor pattern, e.g. as used for event handling. The idea of visitation is to centralize the dynamic casting, so that instead of a zillion dynamic casts peppered throughout the code, there is a single one:

#include <stdio.h>

void say( char const s[] ) { printf( "%s\n", s ); }

struct Event
{
    struct Handler
    {
        virtual void onEvent( Event& ) = 0;
    };

    virtual void dispatchTo( Handler& aHandler )
    {
        aHandler.onEvent( *this );
    }

    template< class SpecificEvent >
    static void dispatch( SpecificEvent& e, Handler& aHandler )
    {
        typedef typename SpecificEvent::Handler SpecificHandler;

        // The single dynamic cast:
        if( SpecificHandler* p = dynamic_cast<SpecificHandler*>( &aHandler ) )
        {
            p->onEvent( e );
        }
        else
        {
            e.Event::dispatchTo( aHandler );
        }
    }
};

struct FooEvent
    : Event
{
    struct Handler
    {
        virtual void onEvent( FooEvent& ) = 0;
    };

    virtual void dispatchTo( Event::Handler& aHandler )
    {
        dispatch( *this, aHandler );
    }
};

struct Plane
    : Event::Handler
{
    virtual void onEvent( Event& ) { say( "An event!" ); }
};

struct Fighter
    : Plane
    , FooEvent::Handler // Comment out this line to get "An event!".
{
    virtual void onEvent( FooEvent& ) { say( "Foo Fighter!" ); }
};

void doThingsTo( Plane& aPlane )
{
    FooEvent().dispatchTo( aPlane );
}

int main()
{
    Fighter plane;

    doThingsTo( plane );
}

The output of this program is Foo Fighter!.

As mentioned, this is simplified. Reality has a tendency to be a bit more messy. And with far more code.

Cheers & hth.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • The [Visitor Pattern](http://en.wikipedia.org/wiki/Visitor_pattern) does not require casting. – Bjarke H. Roune Jul 02 '11 at 04:17
  • 1
    @Bjarke: It's a beautiful thing that you can avoid casting by hardcoding knowledge of all kinds of things that can be visited (e.g. as in the start of the code in the Wikipedia article that you link to, or e.g. as in the example at page 152 in my more or less known [pointers tutorial](https://docs.google.com/fileview?id=0B2oiI2reHOh4M2MzNzYwYzQtMGZkNC00NTljLWJiM2UtOGI0MmRkMTMyZGY4)). But in the general case, hardcoding that knowledge is impractical. Hence I wrote "in general". ;-) – Cheers and hth. - Alf Jul 02 '11 at 08:15
  • By impractical, are you referring to the complication of your linked solution to combining visitors and reference counted pointers? (you can add the smart pointer as a parameter to callBackOn and onCallBack instead) Are you referring to how adding new visited classes can require modifying all the visitors? (if the visitor base class can't offer a default implementation, then it is a feature that the compiler ensures that you modify your visitors) Are you referring to the requirement to modify the visited classes so that they can accept visitors? (good point) Something else? – Bjarke H. Roune Jul 02 '11 at 17:40
  • Bjarke: the last two points you mention, mostly. I didn't even remember writing about smart pointers in visitor pattern. And I don't have time now to check whether your suggestion makes sense / is one I like. Maybe later. I think I shouldn't have written about smart pointer problems there, it now feels to be unrelated... – Cheers and hth. - Alf Jul 02 '11 at 18:28
  • I agree that the last point does rule out the usual way of doing visitors with virtual functions and that dynamic_cast is a reasonable alternative in that case. – Bjarke H. Roune Jul 02 '11 at 18:51
0

I personally use it for working through certain parts of my game engine. I have a base entity class from which I derive various other entities from. I cast them to the base class type so I can store them easily into a linked list. When I want to check to see if a particular entry in my list is of a certain entity, I dynamic_cast it to that type. If it returns null, then I know it's not.

MGZero
  • 5,812
  • 5
  • 29
  • 46
  • 1
    That sounds more like a design headache than an argument for `dynamic_cast`. (And Linked Lists in a game engine?!?) – Billy ONeal Jul 01 '11 at 20:49
0

Let's say we have a library that we're using that is meant for us to derive some types from:

class A {};
class B : public A {};
class C : public B {};

And when we're deriving our types, we have some things that are common to all of our cases:

class CommonStuff {};
class D : public CommonStuff, public C {};

Now, as we're working with our library, and there is a callback that takes type A& (or B& or C&)

void some_func(A& obj);

And assume in that function it expects polymorhpic behavior, but we need to access some of our CommonStuff:

void some_func(A& obj)
{
    CommonStuff& comn = dynamic_cast<CommonStuff&>(obj);
}

Since there's no direct correlation between A and CommonStuff, we cannot use static_cast, reinterpret_cast is obviously not the right choice as it will introduce slicing. The only option here is dyanmic_cast.

Now, take this with a grain of salt, because this could be worked around.

Chad
  • 18,706
  • 4
  • 46
  • 63
  • How would reinterpret_cast cause slicing if it is done on a reference or pointer? As far as I know, [object slicing](http://en.wikipedia.org/wiki/Object_slicing) occurs when a derived type gets copied or assigned *by value* to a supertype. – Bjarke H. Roune Jul 02 '11 at 04:30
  • 1
    Slicing is not the issue, but `reinterpret_cast` is still inappropriate... it does not properly convert pointers to a base type the way `static_cast` will. – Dennis Zickefoose Jul 02 '11 at 07:33
0

You can often replace dynamic_cast<A*>(...) by adding a virtual function to A. However, if A is a class from a third party library then you can't change it so you can't add a virtual function to it. So you may have to use dynamic_cast.

Bjarke H. Roune
  • 3,667
  • 2
  • 22
  • 26