C ++ Developing an Event Handler Class

I am working on building a library. One of the aspects will be EventManager

.

So far, the design looks like this:

Client client
client.on(string, function);

      

Where is the string std::string

, and the function is std::function<void()>

client.on

defined as such:

void Client::on(const std::string& name, const std::function<void()>& function) {
    _eventManager->add(string, function);
}

      

And the _eventManager->add(string, function)

only goal is to create a structure with a name Event

and store the name and function in it, and then push the new rack onto the vector.

Now I can do something similar to this: _eventManager->emit("test");

Then it will loop over the vector array and find any event.name

that is equal to the name you use when calling emit

and run the function inside that structure.

This all works, and it's very cool and awesome and that's it, but it's not really what I need and won't work because I need to send the second argument with _eventManager->emit()

. The second argument will be unknown, but not unknown, but it can be a multiple data type based on the string entered in the first argument.

Usage example:

Client client();

client.on("ready", [](User user) {
    user.test();
});

User user;
_eventManager->emit("ready", user); //I know this does not exists but it is nearly an example.

client.on("message_created", [](Message message) {
    std::cout << message << std::endl; //Operator overload here
}

Message message("Something to print");

_eventManager->emit("message_created", message));

      

I thought about using boost::variant

it to allow multiple types to be allowed to be passed around, however you would need to restore them by doing something like this inside a function.

client.on("ready", [](boost::variant<User, Message> args){
    User user = boost::get<User>(args);
}); 

      

And that should be simple too, I prefer that the person using the library does not use boost::get

in all events to get the passed class.

That being said, with all the great information above, what would be the other alternative to what I want to do? Any suggestions on why I should or shouldn't be doing what I am doing?

It took me a long time to write so thank you in an advanced way and I hope it all makes sense.

+3


source to share


1 answer


I have a solution that uses variable templates (C ++ 14), but this seems like a terrible hack. One of the drawbacks to this is that template variables can only be static. Assuming you can live with him (although that's bad already) ...

For each message type (here type = list of parameters passed to the handler), you will have a separate list of handlers. This can be done as follows:

template<typename F> static map<string, vector<F>> list;

      

Here's an example for F

- this void(int)

is a function that takes one argument int

. Another example is void(int, int)

a function that takes two arguments int

. The data structure I'm using here stores vector

handlers for each message name. Alternatively, I could use multimap

.

When "registering" an event handler, simply add it to the handler list:

template<typename F> static void add_handler(string s, F f)
{
    list<F>[s].push_back(f);
}

      



When looking for event handlers, you must explicitly specify their type. This is the important part that implements type safety - it will only look in the data structure that matches the type of the handler being called.

template<typename F, typename... A> static void call_handlers(string s, A... args)
{
    for (F f: list<F>[s])
        f(args...);
}

      


Using:

// Define event handlers
// Their type SHOULD be explicit (not lambda), because we will reference it further
function<void(int)> action1 = [](int k){cout << "notify user " << k << '\n';};
function<void(int)> action2 = [](int k){cout << "alert user " << k << '\n';};
function<void(int, int)> action3 = [](int a, int b){cout << "give $" << a << " to user " << b << '\n';};

// Register the handlers
add_handler("good_event", action1);
add_handler("good_event", action2);
add_handler("bad_event", action2);
add_handler("money_event", action3);

// Generate events, which will call the handlers
call_handlers<function<void(int)>>("good_event", 7);
call_handlers<function<void(int)>>("bad_event", 8);
call_handlers<function<void(int, int)>>("money_event", 100, 9);

// Wrong call, but no compilation error - the handler is just not found
call_handlers<function<void(int)>>("money_event", 99);

      

+2


source







All Articles