Deprecated: Function set_magic_quotes_runtime() is deprecated in /customers/jogear.net/jogear.net/httpd.www/textpattern/lib/txplib_db.php on line 14
jogear.net: Dynamic Double Dispatch and Templates
Skip to Content »

 Dynamic Double Dispatch and Templates

  • 2008-09-04 09:50

Now that’s a catchy word combination! Yesterday I stumbled on another article, this time in Dr. Dobb’s Journal, by Anthony Williams1. The article shows a potentially good usage pattern for Double Dispatch2 without the usual dependency problems associated with it.

The essence of the article is that by using dynamic_cast, we get a more dynamic (as in runtime resolved) and dependency-free behavior.

Anthony Williams shows that it’ll be necessary to copy-and-paste some boiler plate definitions over and over again. He proceeds by make the burden a bit easier using templates, so that not as much must be copy-and-pasted. However, he didn’t go all the way. He could have taken the template idea one step further to really minimize the need for copy-and-pasting. That’s one extra issue I’ve dealt with here.

With the same base story as in the article, namely that some class should be able to handle messages of polymorphic types, where the actual concrete type is “unknown” (of course, it’s not unknown when using RTTI).

What we want to achieve is thus something like the following. We have some kind of message source (pseudocode) that dispatches message to a target:

void GenerateMessage(Target target)
{
    while(some condition is true)
    {
        if(we have situation A)
        {
            MessageA a;
            a.Dispatch(target);
        }
        else if(we have situation B)
        {
            MessageB b;
            b.Dispatch(target);
        }
        else if(we have situation C)
        {
            MessageC c;
            c.Dispatch(target);
        }

    }
}

where the target looks roughly like this (pseudocode):

class Target
{
    HandleMessage(MessageA a);
    HandleMessage(MessageC c);
}

Notice that the target doesn’t handle any B messages. The original article by Anthony Williams didn’t support this behavior. That’s another issue I’ve dealt with here.

Let’s look at some real C++ code now. First, the target:

class Handler : private MessageHandler::Impl<MessageA>,
                private MessageHandler::Impl<MessageC>
{
public:
    Handler() { }

private:
    void HandleMessage(MessageA&) { }
    void HandleMessage(MessageC&) { }

    Handler(const Handler&);
    Handler& operator=(const Handler&);
};

where the inheritance list and the HandleMessage functions come from this:

namespace MessageHandler
{

    class Base
    {
    protected:
        virtual ~Base() { }
    };

    template <typename T>
    class Impl : public virtual Base
    {
    public:
        virtual void HandleMessage(T& message) = 0;
    };

} // namespace MessageHandler

The actual double dispatch mechanism lies in the message parts:

namespace Message
{

    class Base
    {
    public:
        virtual ~Base() { }

        virtual bool Dispatch(MessageHandler::Base& handler) = 0;

    protected:
        template <typename T>
        bool DynamicDispatch(MessageHandler::Base& handler, T& message)
        {
            MessageHandler::Impl<T>* impl = dynamic_cast<MessageHandler::Impl<T>*>(&handler);

            if(impl != NULL)
            {
                impl->HandleMessage(message);

                return true;
            }
            else
            {
                return false;
            }
        }
    };

    template <typename T>
    class Impl : public Base
    {
    public:
        bool Dispatch(MessageHandler::Base& handler)
        {
            return DynamicDispatch(handler, *static_cast<T*>(this));
        }
    };

} // namespace Message

where the Message::Impl<> class is the first thing I mentioned that the original article was missing to ease the copy-and-paste effort.

With the bases established, we can now define the messages themselves:

class MessageA : public Message::Impl<MessageA> { };
class MessageB : public Message::Impl<MessageB> { };
class MessageC : public Message::Impl<MessageC> { };

And that is basically it. To add a new message and handle it, these are the steps that must be performed (as is done above):

1. Define the new message (required):

class MessageD : public Message::Impl<MessageD> { };

2. Make the handler aware of the new message (optional):

class Handler : private MessageHandler::Impl<MessageD>

3. Add a handler member function for the new message (required only if the handler is aware of the message):

void HandleMessage(MessageD&) { }

If we want to give this a bit more testing, then we can define a simple message queue class:

class Queue
{
public:
    Queue()
    {
        messages_.push(new MessageA);
        messages_.push(new MessageB);
        messages_.push(new MessageC);
    }

    ~Queue()
    {
        while(!IsEmpty())
        {
            Pop();
        }
    }

    bool IsEmpty() const
    {
        return messages_.empty();
    }

    std::auto_ptr<Message::Base> Pop()
    {
        if(IsEmpty())
        {
            return std::auto_ptr<Message::Base>();
        }

        std::auto_ptr<Message::Base> message(messages_.front());
        messages_.pop();

        return message;
    }

private:
    Queue(const Queue&);
    Queue& operator=(const Queue&);

    std::queue<Message::Base*> messages_;
};

and feed it into the handler:

class Handler : private MessageHandler::Impl<MessageA>,
                private MessageHandler::Impl<MessageC>
{
public:
    Handler(Queue& messages)
    {
        while(!messages.IsEmpty())
        {
            messages.Pop()->Dispatch(*this);
        }
    }

private:
    void HandleMessage(MessageA&) { }
    void HandleMessage(MessageC&) { }

    Handler(const Handler&);
    Handler& operator=(const Handler&);
};

which should be easy enough to compile as a console application and step through in the debugger with this wmain to confirm that the B messages pass through unhandled gracefully:

int wmain(int argc, wchar_t* argv[])
{
    Queue messages;
    Handler handler(messages);

    return 0;
}

1 Message Handling Without Dependencies – Don’t shoot the messenger

2 Wikipedia – Double Dispatch