Posted on 09 Feb 2014

Creating an event system in C++

Intro

The other day I decided that I wanted to write the fsm manager I went over before in C++. One of the things in that is the ability to listen to and trigger events, which C++ doesn't have built in to the language like C#. No biggy though, and that is what I am going to go over today!

Observer Pattern

The event system C# is just an implementation of the Observer Pattern. The gist of this pattern is you have an object known as the "Subject", and other objects known as "Observers". The subject contains a list of observers and notifies them of changes when prompted.

Observer Pattern diagram via Wikipedia

In the sense of an event system, you can think of the "subject" as the event and the "observer" as the event handlers you set up to monitor the events.

Implement Observer Pattern based on diagram

First let's implement the basic observer pattern as described by the diagram.

class Observer {
public:
    void notify() {
        cout << "notified!" << endl;
    }
};

class Subject {
    vector<Observer*> observers;

public:
    void addObserver(Observer* observer) {
        this->observers.push_back(observer);
    }

    void removeObserver(Observer* observer) {
        vector<Observer*>::iterator to_remove = this->observers.begin();
        for(; to_remove != this->observers.end(); ++to_remove) {
            if(*to_remove == observer) {
                this->observers.erase(to_remove);
                break;
            }
        }
    }

    void notifyObservers() {
        for(auto o : this->observers) {
            if(o != nullptr)
                o->notify();
        }
    }
};

That is about as basic of an implementation of the observer pattern as you can get. You could change Observer to have virtual methods and use it as a base class, but that's nothing crazy. This pattern can be implemented in many ways, and next we will take a look at how I am going about implementing it for use in an event system.

Influenced by C#'s' event/delegate system

I really like how C# does their event system. That design inspired how I implemented the observer pattern in C++. I am going to show 2 versions of this system here. The first is going to be a more basic version that uses function pointers instead of having the "Observer" be a class.

using EventHandler = void(*)();

This function pointer must point to a function with a return type of void and an argument list of void. You can also assign a lambda function to a function pointer as long as you are not capturing anything in the lambda.

Now for the "Subject" equivalent we are going to create a class that is very similar to the basic implementation above. The changes you'll see are more for convenience of use.

class Event {
    vector<unique_ptr<EventHandler>> handlers;

    void notifyHandlers() {
        vector<unique_ptr<EventHandler>>::iterator func = this->handlers.begin();
        for(; func != this->handlers.end(); ++func) {
            if(*func != nullptr) {
                (*(*func))();
            }
        }
    }
public:
    void addHandler(const EventHandler &handler) {
        this->handlers.push_back(unique_ptr<EventHandler>(new EventHandler{handler}));
    }

    void removeHandler(const EventHandler &handler) {
        vector<unique_ptr<EventHandler>>::iterator to_remove = this->handlers.begin();
        for(; to_remove != this->handlers.end(); ++to_remove) {
            if(*(*to_remove) == handler) {
                this->handlers.erase(to_remove);
                break;
            }
        }
    }

    void operator()() {
        this->notifyHandlers();
    }

    Event &operator+=(const EventHandler &handler) {
        this->addHandler(handler);

        return *this;
    }

    Event &operator-=(const EventHandler &handler) {
        this->removeHandler(handler);

        return *this;
    }
};

You can easily extend this to allow the passing of arguments to the handlers. It would be as simple as changing the function pointer definition, and adjusting the operator() and notifyHandlers() to accept and use the required arguments. Instead of having to do all that though, let's take a look at another more flexible way of going about this.

In C++11 there is the std::function type which can hold any object you can invoke using the call operator (), i.e. a function object. Given the fact that a std::function object can store any callable object, and not all callables are comparable, it isn't possible to compare them for equality. Like many things in programming there are various ways to go about solving this, and here is one of those ways!

First let's take a look at the class that we wrap our function object in.

class EventHandler {
public:
    using Func = std::function<void()>;

private:
    Func _func;

public:
    int id;
    static int counter;

    EventHandler() : id{0} {
    }

    EventHandler(const Func &func) : _func{func} {
        this->id = ++EventHandler::counter;
    }

    void operator()() {
        this->_func();
    }

    void operator=(const EventHandler &func) {
        if(this->_func == nullptr) {
            this->_func = func;
            this->id = ++EventHandler::counter;
        } else {
            // Throw as exception or just log it out.
            std::cerr << "Nope!" << std::endl;
        }
    }

    bool operator==(const EventHandler &del) {
        return this->id == del.id;
    }
    bool operator!=(nullptr_t) {
        return this->_func != nullptr;
    }
};

The reason we wrap the std::function object in this class is because we want to be able to have the ability to remove a handler from listening to the event. That requires some way of finding the correct one in the vector of function objects. To do this I added the int id and static int counter variables to the class. Each time a EventHandler is created with a valid callable to capture in the _func member variable, or is assigned a callable matching the signature of Func and doesn't already have one, it increases the counter by 1 and then assigns the current counter value to the id. Everything else in the class should probably be pretty self-explanatory.

The Event class for this version is very similar to the previous implementation, with a slight change in notifyHandlers and the addition of a second operator+= overload.

class Event {
    std::vector<std::unique_ptr<EventHandler>> handlers;

    void notifyHandlers() {
        vector<unique_ptr<EventHandler>>::iterator func = this->handlers.begin();
        for(; func != this->handlers.end(); ++func) {
            if(*func != nullptr && (*func)->id != 0) {
                (*(*func))();
            }
        }
    }
public:
    void addHandler(const EventHandler &handler) {
        this->handlers.push_back(unique_ptr<EventHandler>(new EventHandler{handler}));
    }

    void removeHandler(const EventHandler &handler) {
        vector<unique_ptr<EventHandler>>::iterator to_remove = this->handlers.begin();
        for(; to_remove != this->handlers.end(); ++to_remove) {
            if(*(*to_remove) == handler) {
                this->handlers.erase(to_remove);
                break;
            }
        }
    }

    void operator()() {
        this->notifyHandlers();
    }

    Event &operator+=(const EventHandler &handler) {
        this->addHandler(handler);

        return *this;
    }

    Event &operator+=(const EventHandler::Func &handler) {
        this->addHandler(EventHandler{handler});

        return *this;
    }

    Event &operator-=(const EventHandler &handler) {
        this->removeHandler(handler);

        return *this;
    }
};

First up let's direct your attention to operator+=(const EventHandler::Func &handler) overload. This enables the ability to add anything that the EventHandler::Func type can hold, in addition to adding a full blown EventHandler object with the other overloaded operator+=.

Further adjustments/optimizations

There are a few things that may need to be adjusted in the code depending on how it is used, however I didn't want to make this code any more complicated that it needs to be for clarity's sake. For one, there is a potential race condition with the static int member variable when used in a multithreaded program, so to account for this you would need to deal with the static int appropriately. Something else is that if you get in a situation where you are adding and removing a large amount of handlers to an event it may be worth looking at using a container other than vector.

Wrapping it up!

As I use this code more there may need to be some tweaks made, but this is my initial way of tackling the problem. I hope that this may help anyone that is looking to do something similar, and if you have any feedback please feel free to leave comments or shoot me an email. You can check out the repository for this here, and it is also listed on my projects page.