A simple EventEmitter in C++

EventEmitter coming with node.js is handy most of the time, and I implement this useful utility in C++ after finishing writing Functor. Yes, we need Functor to make it much more easier to write this fabulous utility. (Why don't you use lamdba? I'll talk about that later in this post)

It's extremely simple to use EventEmitter in node.js

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
    console.log('an event occurred!');
});
myEmitter.emit('event');

——https://nodejs.org/api/events.html

And the C++ version of EventEmitter need to be as easy as it is in node.js. As a matter of fact, this class made it.

#include <iostream>
#include <sstream>
#include <thread>
#include <vector>
#include "EventEmitter.hpp"

using namespace std;

class emitter : public EventEmitter {
};

int main(int argc, const char * argv[]) {
    emitter emitter;
    emitter.on("event", [&emitter](int data) {
        ostringstream osstream;
        osstream << "data: " << data << '\n';
        std::cout << osstream.str();
    });

    vector<thread> threads;
    for (int i = 0; i < 10; i++) {
        threads.emplace_back([&emitter, i]() {
            emitter.emit("event", i);
        });
    }

    for (auto &t : threads) t.join();
}

In fact, to give the same functionality in C++ version is rather straightforward. Just use std::map, and we map event string to Functor *. When an event emits, fetch the Functor * from event map, if it's not a nullptr, then it will be called.

The reason I wrote Functor is that according to different requirements, these callbacks differ from each other. Even we can use std::function to wrap they when they happens to have the same paramter list. But remember we're creating a universal event emitter, we have to accept various types of lambda.

And, these codes below are not allowed in C++ (yet, at least).

    auto lambda = []() {

    };
    typedef std::map<std::string, decltype(lambda)> function_map;

    function_map event{"event", [](){}}; // won't compile

    function_map event;
    event["event"] = lambda; // won't compile, too

As you can see, a lambda wrapper is highly demanded. So here comes the Functor.

The result of previous example codes (Lines may be shuffled due to the timing of thread's execution)

EventEmitter
EventEmitter

Finally, codes below are EventEmitter.hpp. Newest version on my Github EventEmitter.

//
//  EventEmitter.hpp
//  EventEmitter
//
//  Created by Ryza 2016/8/6.
//  Copyright © 2016[data deleted]. All rights reserved.
//

#ifndef EVENTEMITTER_HPP
#define EVENTEMITTER_HPP

#include <map>
#include <string>
#include "Functor.hpp"

class EventEmitter {
public:
    /**
     *  @brief Deconstructor
     */
    ~EventEmitter() {
        std::for_each(events.begin(), events.end(), [](std::pair<std::string, Functor *> pair) {
            delete pair.second;
        });
        events.clear();
    }

    /**
     *  @brief Event setter
     *
     *  @param event  Event name
     *  @param lambda Callback function when event emitted
     */
    template <typename Function>
    void on(const std::string& event, Function&& lambda) {
        events[event] = new Functor{std::forward<Function>(lambda)};
    }

    /**
     *  @brief Event emitter
     *
     *  @param event  Event name
     */
    template <typename ... Arg>
    void emit(const std::string& event, Arg&& ... args) {
        Functor * on = events[event];
        if (on) (*on)(std::forward<Arg>(args)...);
    }

    /**
     *  @brief Event name - Callback function
     */
    std::map<std::string, Functor *> events;

protected:
    /**
     *  @brief Constructor
     */
    EventEmitter() {
    };
};

#endif /* EVENTEMITTER_HPP */
 

Leave a Reply

Your email address will not be published. Required fields are marked *

1 + three =