I am going to show you three ways in which a messaging framework might be implemented; the first is the ‘proper’ idiomatic C++ way; in the second and third parts, a more efficient blending of C & C++ styles (but may make C++ purists choke on their breakfast).
- There are a set of N messages that can be passed around the application.
- The messages are all derived from a base message class.
- Objects in the application must be able to receive and process messages.
- Objects receiving messages are derived from a base message processor class.
- Message processors receive messages as references to the base message class.
- They will not all processes the same number or combination of messages.
Let’s start off with the pure object orientated C++ version.
It is usually deemed a ‘code smell’ in C++ programming if code has to interrogate a base object to determine its concrete derived type. As messages are passed to message processors as references to the base message type, some way must be created to steer the message to the correct handler. One way of doing this is to use ‘double dispatch’ or the Visitor Pattern.
Is you are not familiar with this pattern, then hopefully this metaphor will give you the basic idea.
Imagine a salesman visits a company. He meets management, sales, marketing and engineering. He gives out business cards to each person he meets. There is a different telephone help line for each type of recipient, one for management, one for sales etc. Now, he could have a different card for each type of recipient, which would require him to know the job title of each person he meets. He would have to ask them what they did and choose the correct card for each one.
Alternatively, he could have one card with all of the telephone numbers on it and tell each recipient to “call the help line appropriate to your job title”.
This is effectively what the visitor pattern achieves. It says to the message “Here is an interface of overloaded functions that can accept all of the messages. Call the one that matches your concrete type”.
An interface is created that defines a virtual handler function for every message. The handlers in the interface may have default bodies, maybe logging an unhandled message. The concrete message processor inherits from this and implements each of the message handlers it is interested in. When a reference to a base message object is received it will pass itself to the message and the message will call the correct handler in the message processor.
Let’s show a little bit of code as an example.
The code below defines a visitor interface that defines virtual handlers for four message types. The virtual handlers have default bodies that send control to an ‘Unhandled’ member (pure virtual). Two message processors are defined, one handling all of the messages, the other just Message1 and Message3.
Lets exercise this code.
The output of this test will be…
Processor1 : Message1 Processor1 : Message2 Processor1 : Message3 Processor1 : Message4 Processor2 : Message1 Processor2 : Unhandled IMessage Processor2 : Message3 Processor2 : Unhandled IMessage
This is a neat solution and does not require any message processor to interrogate the received message to determine its type.
There an issue with this scheme that may impact the implementation on a resource limited embedded platform. The visitor interface IMessageProcessor must contain a virtual handler function for every message that exists. The vtable for this may become prohibitively large when there are a substantial number of message types. Each derived message processor will have its own copy of the vtable. This will be true even if the derived message processor only handles one message! This may invoke an unacceptable resource overhead.
Also there is the coupling between the visitor interface and the message types. It would be nice if we could make things a little more decoupled.
In part 2 I will describe the first alternative that uses templates and template specialisation to address some of these issues.