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_cast
s 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:
using MsgPtr = std::unique_ptr<Message>;
MsgPtr msg = ... // any custom message object;
Handler handler;
msg->dispatch(handler); // will invoke right handling function.
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:
virtual void dispatchImpl(Handler& handler) override
{
handler.handle(*this);
}
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.
class Message
{
public:
void dispatch(Handler& handler)
{
dispatchImpl(handler);
}
...
protected:
virtual void dispatchImpl(Handler& handler) = 0;
};
template <typename TDerived>
class MessageBase : public Message
{
protected:
virtual void dispatchImpl(Handler& handler) override
{
handler.handle(static_cast<Derived&>(*this));
}
}
class ActualMessage1 : public MessageBase<ActualMessage1>
{
...
};
class ActualMessage2 : public MessageBase<ActualMessage2>
{
...
};
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:

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:
class Handler
{
public:
void handle(ActualMessage2& msg) {...}
void handle(ActualMessage5& msg) {...}
void handle(Message& msg) {} // empty body
}
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.
class Handler
{
public:
virtual void handle(ActualMessage1& msg) = 0;
virtual void handle(ActualMessage2& msg) = 0;
...
}
class ActualHandler1 : public Handler
{
public:
virtual void handle(ActualMessage1& msg) override;
virtual void handle(ActualMessage2& msg) override;
...
}
class ActualHandler2 : public Handler
{
public:
virtual void handle(ActualMessage1& msg) override;
virtual void handle(ActualMessage2& msg) override;
...
}
No other changes to dispatch functionality is required:
using MsgPtr = std::unique_ptr<Message>;
MsgPtr msg = ... // any custom message object;
AtualHandler1 handler1;
AtualHandler2 handler2;
// Works for any handler
msg->dispatch(handler1);
msg->dispatch(handler2);
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:
using AllMessages = std::tuple<
ActualMessage1,
ActualMessage2,
...
>;
Then the definition of the generic handling class will be as following:
// TCommon is common interface class for all the messages
// TAll is all the message types, that need to be handled, bundled in std::tuple
template <typename TCommon, typename TAll>
class GenericHandler;
template <typename TCommon, typename TFirst, typename... TRest>
class GenericHandler<TCommon, std::tuple<TFirst, TRest...> > :
public GenericHandler<TCommon, std::tuple<TRest...> >
{
using Base = GenericHandler<TCommon, std::tuple<TRest...> >;
public:
using Base::handle; // Don't hide all handle() functions from base classes
virtual void handle(TFirst& msg)
{
// By default call handle(TCommon&)
this->handle(static_cast<TCommon&>(msg));
}
};
template <typename TCommon>
class GenericHandler<TCommon, std::tuple<> >
{
public:
virtual ~GenericHandler() {}
virtual void handle(TCommon&)
{
// Nothing to do
}
};
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
class Handler : public GenericHandler<Message, AllMessages> {};
is equivalent to having the following class defined:
class Handler
{
public:
virtual void handle(ActualMessage1& msg)
{
this->handle(static_cast<Message&>(msg));
}
virtual void handle(ActualMessage2& msg)
{
this->handle(static_cast<Message&>(msg));
}
...
virtual void handle(Message& msg)
{
// do nothing
}
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:
class ActualHandler1 : public Handler
{
public:
virtual void handle(ActualMessage2& msg) override
{
std::cout << "Handling ActualMessage2" << std::endl;
}
virtual void handle(Message& msg) override
{
std::cout << "Common handling function is invoked" << std::endl;
}
}
REMARK: Remember that the Handler
class was forward declared when defining the Message
interface class? Usually it looks like this:
class Handler;
class Message
{
public:
void dispatch(Handler& handler) {...}
};
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 vtable
s 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.
template <typename TCommon, typename TAll>
class GenericHandler;
template <typename TCommon, typename TFirst, TSecond, TThird, typename... TRest>
class GenericHandler<TCommon, std::tuple<TFirst, TSecond, TThird, TRest...> > :
public GenericHandler<TCommon, std::tuple<TRest...> >
{
using Base = GenericHandler<TCommon, std::tuple<TRest...> >;
public:
using Base::handle;
virtual void handle(TFirst& msg)
{
this->handle(static_cast<TCommon&>(msg));
}
virtual void handle(TSecond& msg)
{
this->handle(static_cast<TCommon&>(msg));
}
virtual void handle(TThird& msg)
{
this->handle(static_cast<TCommon&>(msg));
}
};
template <typename TCommon, typename TFirst, typename TSecond>
class GenericHandler<TCommon, std::tuple<TFirst, TSecond> >
{
public:
virtual ~GenericHandler() {}
virtual void handle(TFirst& msg)
{
this->handle(static_cast<TCommon&>(msg));
}
virtual void handle(TSecond& msg)
{
this->handle(static_cast<TCommon&>(msg));
}
virtual void handle(TCommon&)
{
// Nothing to do
}
};
template <typename TCommon, typename TFirst>
class GenericHandler<TCommon, std::tuple<TFirst> >
{
public:
virtual ~GenericHandler() {}
virtual void handle(TFirst& msg)
{
this->handle(static_cast<TCommon&>(msg));
}
virtual void handle(TCommon&)
{
// Nothing to do
}
};
template <typename TCommon>
class GenericHandler<TCommon, std::tuple<> >
{
public:
virtual ~GenericHandler() {}
virtual void handle(TCommon&)
{
// Nothing to do
}
};
Last updated
Was this helpful?