How do I avoid big switches in my message system?

I've been implementing a message system for my small engine, and I have started to think about optimizing and maintaining it. Right now, my message class looks like this (at its simplest - only member variables):

typedef unsigned int MessageID;

class Message
{
std::string message;
MessageID messageID;
MessageData* data;
};


At first I was only using std::string to recognize each event, and handling was done in cascade-if style:

void GUI_system::handleMessage(Message* message)
{
if (message->message == "DELETE_HP")
{
deleteHearth(static_cast<AttInfo*>(message->data));

}
else if (message->message == "PAUSE_GAME")
{
showPauseIcon();
}
}


However, I felt it is uncomfortable to work with, and for a switch I needed a numeric variable. This is why is MessageID is generated from unordered_map each time a message is created; each message has its own ID, and if not, a new one is created. So now it looks like this:

void GUI_system::handleMessage(Message* message)
{
switch(message->messageID)
{
case 1 :
{
deleteHearth(static_cast<AttInfo*>(message->data));
break;
}
case 2 :
{
showPauseIcon();
break;
}
}
}


However, this was more confusing than the cascade-if style, where I at least had a little knowledge of what is going on, internally, apart from a plain int. I know enumerations are easier to use and implement, but this was the first method I tried, and I also wanted to try it the other way.

This brings me to my question; how do I avoid big switches in my message system? Should I just use enums?

My first suggestion was to create an unordered map of function pointers that define the behavior for a message, where the MessageIDs would act as keys, so the initialisation function would be defined in some init function, like this:

typedef void* func(Message*) messageF;

std::unordered_map<MessageID,messageF> _messageMap;

void GUI_system::InitBehaviorFunctions()
{
_messageMap[MessageIDMapper::findID("DELETE_HP")] = deleteHearth;
_messageMap[MessageIDMapper::findID("PAUSE_GAME")] = showPauseIcon;
}

void GUI_system::handleMessage(Message* message)
{
//Or the right way as this is just a sketch
_messageMap[message->messageID](message);
}


If you go with enums, you could just make an array of function pointers to call for each value instead of a map. Something like this:

typedef enum {
Delete_HP = 0,
Pause_Game,
//... etc.
} MessageID;


Then the array of function pointers:

typedef void* function(Message*) messageF;
messageF msgHandlers[] = {
deleteHearth,
showPauseIcon,
// ... etc.
};


Then your message handling function becomes something like:

void GUI_system::handleMessage(Message* message) {
msgHandlers[message->messageID](message);
}


This is very similar to your map example, except there's no function call overhead for the lookup, and the setup is just adding the function name to the array in the correct position. It's harder to keep updated if you delete messages frequently, but very straightforward to understand.

• Nice point with the array of enums! But on the other hand, I´m a little bit scared to use the enums because of the order dependency while indexing the array and as you said it could be harder to maintain it if deleting messages frequently(and I cannot predict if it happens because until now I have been working with just a few messages to test it out) – Pins Apr 27 '17 at 16:54
• @Pins: As long as you recompile all occurrences where the enum is used this will be no problem. Otherwise when you build the whole project and an enum value is missing the compiler will throw an error. Only if you load DLLs dynamically it might get dangerous. But having a version number might help avoid loading outdated files. – Skalli Apr 28 '17 at 15:04
• Instead of pure function pointers I had to use std::function so its easier to manipulate with member functions. If anyone wondered. – Pins Apr 29 '17 at 18:38