I’m creating a mechanism by which Receivers can tell a Sender that each Receiver is interested in Messages of a certain type. With my sample implementation below there exists a limitation where a Receiver that wants to receive all Messages of a certain base type only receives Messages that are explicitly of that type and will not receive Messages of a derived type (see main() for example).
A potential solution would be to register all of a Message’s ancestors’ types when registering that particular Message and use that information to route Messages properly.
What other solutions are there?
Note: In reality, I’d store the RTTI so a RTTI lookup wouldn’t be required every time. There are also other things that I have skimped/skipped here, as well. I’m going for brevity w/ this example…
Example code below:
class Sender
{
typdef std::vector<Receiver const & > Receivers;
public:
void register(Receiver const & i_recv, typeinfo const & i_type)
{
m_routingMap[i_type].push_back(i_recv);
}
void send(BaseMsg const & i_msg)
{
Receivers receivers = m_routingMap.find(typeid(i_msg));
for (Receivers::iterator receiver = receivers.begin(); receiver != receivers.end(); ++receiver) {
receiver.receive(i_msg);
}
}
private:
std::map<typeinfo const &, Receivers> m_routingMap;
};
class Receiver
{
public:
void receiver(BaseMsg const & i_msg)
{
// React to expected messages here
}
};
class BaseMsg {};
class ChildMsg : public BaseMsg {};
int main()
{
Sender sndr;
Receiver recv1;
sndr.register(recv1, typeid(BaseMsg));
Receiver recv2;
sndr.register(recv2, typeid(ChildMsg));
BaseMsg baseMsg;
sndr.send(baseMsg); // I want only recv1 to receive this message
ChildMsg childMsg;
sndr.send(childMsg); // I want both Receivers to receive this message, but only recv2 will receive it
}
Update: here’s a solution I’m getting up to:
// Note: implementation is based in gleaning from
// http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.14
class BaseMsg
{
public:
typedef std::vector<TypeInfo const & > Types;
static TypeInfo const * getType()
{
TypeInfo static * ms_type = new TypeInfo(typeid(BaseMsg));
return ms_type;
}
static Types const * getAncestorTypes()
{
// The base class does not have an ancestor
// Static varible, will only be constructed once!
Types * ms_ancestorTypes = new Types();
return ms_ancestorTypes;
}
};
class ChildMsg
{
public:
static TypeInfo const * getType()
{
TypeInfo static * ms_type = new TypeInfo(typeid(ChildMsg));
return ms_type;
}
static Types const * getAncestorTypes()
{
// Add the parent type and all the parent's ancestor's types
Types const * ancestorTypes = BaseMsg::getAncestorTypes();
// Static variable, so it will only be constructed once!
Types * static ms_ancestorTypes = new Types(ancestorTypes->begin(), ancestorTypes->end());
// This push_back() will occur every time, but it's only one operation,
// so hopefully it's not a big deal!
ms_ancestorTypes->push_back(BaseMsg::getType());
return ms_ancestorTypes;
}
};
And the Sender:
# Python pseudo code
class Sender:
def send(self, i_msg):
types_to_check_for = [i_msg.getType()].extend(i_msg.getAncestorTypes())
for type_ in types_to_check_for:
for receiver in _routing_list[type_]:
receiver.receive(i_msg)
Perhaps consider using an observer pattern (http://en.wikipedia.org/wiki/Observer_pattern).
This way you sender has no knowledge of your receiver, and your observer can control the distribution of msgs.
Sender -> informs observer there is a message.
observer -> informs each interested party there is a new msg.
interested part -> does fun stuff.
This will require some sort of msg identification system. Perhaps all msgs could inherit from a msg type that has a type member and an id member. That way you can register for msgs using them.
Update:
A quick msg structure:
The subscriber means the person that wants to listen to the msg). The subscriber should have an interface to accept msgs based on both of these functions.
The observer should have a method to register for the msgs:
Another Update:
This is really just a rough proof-of-concept. One can do what you want without knowing the exact ancestor chain. Forgive the switching of the trigger and registrationEntry functions (I did it wrong at first, and that was the simplest correction, again proof-of-concept). Another downside of this sketch is that at least a msg has to be constructed to register. If you are looking for a real long term solution, I suggest you find a library, or framework, that has reflection in it already (QT for example has the metaobjects), these could be used to see superclasses. Or, you could use the signals/slots already there.
Output from the code below:
Starting C:\Users\David\Downloads\asdf-build-desktop-Qt_4_8_0_for_Desktop_-MinGW_Qt_SDK__Release\release\asdf.exe…
Base Register
Registration: BaseMsg
Child Register
Registration: Message
Base call
Trigger: BaseMsg
virtual void Subscriber1::newMessage(const BaseMsg&)
Der. call
Trigger: BaseMsg
virtual void Subscriber1::newMessage(const BaseMsg&)
Trigger: Message
virtual void Subscriber2::newMessage(const BaseMsg&)
C:\Users\David\Downloads\asdf-build-desktop-Qt_4_8_0_for_Desktop_-MinGW_Qt_SDK__Release\release\asdf.exe exited with code 0