Skip to content

Instantly share code, notes, and snippets.

@rioki
Last active August 13, 2023 09:05
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save rioki/1290004d7505380f2b1d to your computer and use it in GitHub Desktop.
Save rioki/1290004d7505380f2b1d to your computer and use it in GitHub Desktop.
node.js' style EventEmitter for C++ (http://www.rioki.org/2014/12/29/eventemitter-in-c.html)
//
// Copyright (c) 2014 Sean Farrell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include "EventEmitter.h"
#include <stdexcept>
EventEmitter::EventEmitter() {}
EventEmitter::~EventEmitter() {}
unsigned int EventEmitter::add_listener(unsigned int event_id, std::function<void ()> cb)
{
if (!cb)
{
throw std::invalid_argument("EventEmitter::add_listener: No callbak provided.");
}
std::lock_guard<std::mutex> lock(mutex);
unsigned int listener_id = ++last_listener;
listeners.insert(std::make_pair(event_id, std::make_shared<Listener<>>(listener_id, cb)));
return listener_id;
}
unsigned int EventEmitter::on(unsigned int event_id, std::function<void ()> cb)
{
return add_listener(event_id, cb);
}
void EventEmitter::remove_listener(unsigned int listener_id)
{
std::lock_guard<std::mutex> lock(mutex);
auto i = std::find_if(listeners.begin(), listeners.end(), [&] (std::pair<const unsigned int, std::shared_ptr<ListenerBase>> p) {
return p.second->id == listener_id;
});
if (i != listeners.end())
{
listeners.erase(i);
}
else
{
throw std::invalid_argument("EventEmitter::remove_listener: Invalid listener id.");
}
}
//
// Copyright (c) 2014 Sean Farrell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#ifndef _EVENT_EMITTER_H_
#define _EVENT_EMITTER_H_
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <list>
#include <algorithm>
class EventEmitter
{
public:
EventEmitter();
~EventEmitter();
template <typename... Args>
unsigned int add_listener(unsigned int event_id, std::function<void (Args...)> cb);
unsigned int add_listener(unsigned int event_id, std::function<void ()> cb);
template <typename... Args>
unsigned int on(unsigned int event_id, std::function<void (Args...)> cb);
unsigned int on(unsigned int event_id, std::function<void ()> cb);
void remove_listener(unsigned int listener_id);
template <typename... Args>
void emit(unsigned int event_id, Args... args);
private:
struct ListenerBase
{
ListenerBase() {}
ListenerBase(unsigned int i)
: id(i) {}
virtual ~ListenerBase() {}
unsigned int id;
};
template <typename... Args>
struct Listener : public ListenerBase
{
Listener() {}
Listener(unsigned int i, std::function<void (Args...)> c)
: ListenerBase(i), cb(c) {}
std::function<void (Args...)> cb;
};
std::mutex mutex;
unsigned int last_listener;
std::multimap<unsigned int, std::shared_ptr<ListenerBase>> listeners;
EventEmitter(const EventEmitter&) = delete;
const EventEmitter& operator = (const EventEmitter&) = delete;
};
template <typename... Args>
unsigned int EventEmitter::add_listener(unsigned int event_id, std::function<void (Args...)> cb)
{
if (!cb)
{
throw std::invalid_argument("EventEmitter::add_listener: No callbak provided.");
}
std::lock_guard<std::mutex> lock(mutex);
unsigned int listener_id = ++last_listener;
listeners.insert(std::make_pair(event_id, std::make_shared<Listener<Args...>>(listener_id, cb)));
return listener_id;
}
template <typename... Args>
unsigned int EventEmitter::on(unsigned int event_id, std::function<void (Args...)> cb)
{
return add_listener(event_id, cb);
}
template <typename... Args>
void EventEmitter::emit(unsigned int event_id, Args... args)
{
std::list<std::shared_ptr<Listener<Args...>>> handlers;
{
std::lock_guard<std::mutex> lock(mutex);
auto range = listeners.equal_range(event_id);
handlers.resize(std::distance(range.first, range.second));
std::transform(range.first, range.second, handlers.begin(), [] (std::pair<const unsigned int, std::shared_ptr<ListenerBase>> p) {
auto l = std::dynamic_pointer_cast<Listener<Args...>>(p.second);
if (l)
{
return l;
}
else
{
throw std::logic_error("EventEmitter::emit: Invalid event signature.");
}
});
}
for (auto& h : handlers)
{
h->cb(args...);
}
}
#endif
@rioki
Copy link
Author

rioki commented Jan 5, 2015

Yes, you can find the details on the implementation and use under http://www.rioki.org/2014/12/29/eventemitter-in-c.html

But in your case, you need to specify the types:

emitter.on<int>(0, [](int err){ 
    cout << "on error" <<1<< endl;
});

This is sort of dumb thing by C++. You either must specify the type on the template function or wrap it into a std::function object: C++'s template type inference does not work well when working with lambdas.

@martinfinke
Copy link

The solution (with on<int>) doesn't work for me (Xcode 6.2, LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)). I copy & pasted the example from above and replaced the call with your suggestion.

The error is:

No matching member function for call to 'on'

Do you know how to solve this?

[EDIT] Nevermind, got it to work with a nice solution from Stack Overflow. Here if you're interested.

@ArielSaldana
Copy link

How do I return other values, besides int?
for example -

emitter.emit(0, "Test String");

Edit

In case anyone else runs into the problem the solution is :

EventEmitter emitter;

emitter.on<std::string>(0, [](std::string err) {
        std::cout << err << std::endl;
});

std::string str = "pass string";
emitter.emit(0, str);

@rioki
Copy link
Author

rioki commented Jun 28, 2017

Or, you force the template type:

emitter.emit<std::string>(0, "pass string");

@anhldbk
Copy link

anhldbk commented Jun 30, 2017

Hey @rioki, how can I register a non-static event handler? The code below can't be compiled

class Data: public EventEmitter{
public:
    void activate(){
        this->on(1, this->show);     // -> error: invalid use of non-static member function
    }
    
    void show(const char* message){
        cout << message << endl;        
    }
};

Update
I've managed to invoke it correctly (with std::bind)

class Data: public EventEmitter{
public:
    void activate(){
        std::function<void(const char*)> handler = std::bind(&Data::show, this, std::placeholders::_1);        
        this->on(1, handler);        
    }
    
    void show(const char* message){
        cout << message << endl;        
    }
};

@rioki
Copy link
Author

rioki commented Nov 20, 2018

@anhldbk Alternatively:

class Data: public EventEmitter
{
public:
    void activate()
    {
        this->on(1, [this] (const char* message) {show(msg);});        
    }
    
    void show(const char* message)
    {
        cout << message << endl;        
    }
};

@dacatchman
Copy link

@anhldbk Alternatively:

class Data: public EventEmitter
{
public:
    void activate()
    {
        this->on(1, [this] (const char* message) {show(msg);});        
    }
    
    void show(const char* message)
    {
        cout << message << endl;        
    }
};

Mmm, that compiles for you? When I try that it says cannot convert from lambda to std::function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment