Dispatching and Handling

When a new message arrives, its appropriate object is created, and the contents are deserialised using read() member function, described in previous chapter. It is time to dispatch it to an appropriate handling function. Many developers use the switch statement or even a sequence of dynamic_casts to identify the real type of the message object and call appropriate handling function.

As you may have guessed, this is pretty inefficient, especially when there are more than 7-10 messages to handle. There is a much better way of doing a dispatch operation by using a C++ ability to differentiate between functions with the same name but with different parameter types. It is called Double Dispatch Idiom.

Let's assume we have a handling class Handler that is capable of handling all possible messages:

class Handler
{
public:
    void handle(ActualMessage1& msg);
    void handle(ActualMessage2& msg);
    ...
}

Then the definition of the messages may look like this:

class Message 
{
public:
    void dispatch(Handler& handler)
    {
        dispatchImpl(handler);
    }
    ...

protected:
    virtual void dispatchImpl(Handler& handler) = 0; 
};

class ActualMessage1 : public Message 
{
    ...
protected:
    virtual void dispatchImpl(Handler& handler) override
    {
        handler.handle(*this); // invokes handle(ActualMessage1&);
    }
};

class ActualMessage2 : public Message 
{
    ...
protected:
    virtual void dispatchImpl(Handler& handler) override
    {
        handler.handle(*this); // invokes handle(ActualMessage2&);
    }
};

Then the following code will invoke appropriate handling function in the Handler object:

Please note, that the Message interface class doesn't require the definition of the Handler class, the forward declaration of the latter is enough. The Handler also doesn't require the definitions of all the actual messages being available, forward declarations of all the message classes will suffice. Only the implementation part of the Handler class will require knowledge about the interface of the messages being handled. However, the public interface of the Handler class must be known when compiling dispatchImpl() member function of any ActualMessageX class.

Eliminating Boilerplate Code

You may also notice that the body of all dispatchImpl() member functions in all the ActualMessageX classes is going to be the same:

The problem is that *this expression in every function evaluates to the object of different type.

The apperent code duplication may be eliminated using Curiously Recurring Template Pattern idiom.

Please note, that ActualMessageX provide their own type as a template parameter to their base class MessageBase and do not require to implement dispatchImpl() any more. The class hierarchy looks like this:

Image: Class hierarchy

Handling Limited Number of Messages

What if there is a need to handle only limited number of messages, all the rest just need to be ignored. Let's assume the protocol defines 10 messages: ActualMessage1, ActualMessage2, ..., ActualMessage10. The messages that need to be handled are just ActualMessage2 and ActualMessage5, all the rest ignored. Then the definition of the Handler class will look like this:

In this case, when compiling dispatchImpl() member function of ActualMessage2 and ActualMessage5, the compiler will generate invocation code for appropriate handle() function. For the rest of the message classes, the best matching option will be invocation of handle(Message&).

Polymorphic Handling

There may be a need to have multiple handlers for the same set of messages. It can easily be achieved by making the Handler an abstract interface class and defining its handle() member functions as virtual.

No other changes to dispatch functionality is required:

Generic Handler

Now it's time to think about the required future effort of extending the handling functionality when new messages are added to the protocol and their respective classes are implemented. It is especially relevant when Polymorphic Handling is involved. There is a need to introduce new virtual handle(...) member function for every new message that is being added.

There is a way to delegate this job to the compiler using template specialisation. Let's assume, that all the message types, which need to be handled, are bundled into a simple declarative statement of std::tuple definition:

Then the definition of the generic handling class will be as following:

The code above generates virtual handle(TCommon&) function for the common interface class, which does nothing by default. It also creates a separate virtual handle(...) function for every message type provided in TAll tuple. Every such function upcasts the message type to its interface class TCommon and invokes the handle(TCommon&).

As the result simple declaration of

is equivalent to having the following class defined:

From now on, when new message class is defined, just add it to the AllMessages tuple definition. If there is a need to override the default behaviour for specific message, override the appropriate message in the handling class:

REMARK: Remember that the Handler class was forward declared when defining the Message interface class? Usually it looks like this:

Note, that Handler is declared to be a class, which prevents it from being a simple typedef of GenericHandler. Usage of typedef will cause compilation to fail.

CAUTION: The implementation of the GenericHandler presented above creates a chain of N + 1 inheritances for N messages defined in AllMessages tuple. Every new class adds a single virtual function. Many compilers will create a separate vtable for every such class. The size of every new vtable is greater by one entry than a previous one. Depending on total number of messages in that tuple, the code size may grow quite big due to growing number of vtables generated by the compiler. It may be not suitable for some systems, especially bare-metal. It is possible to significantly reduce number of inheritances using more template specialisation classes. Below is an example of adding up to 3 virtual functions in a single class at once. You may easily extend the example to say 10 functions or more.

Last updated

Was this helpful?