C# like events in C++ - second approach

Posted on Sat 24 March 2018 in C/C++

Hi Everyone ! About year ago I try to implement events for 2DXngine. Right now when I try to implement game I realize that this implementation is insufficient for my current needs. Previous events need to store binding in order to unsubscribe, use macro to create bindings and so on. This lead me to try to implement new events. Let see how I want to use events:

class TestPublisherClass
{
public:

    Event<EventArgs> evt;

    void fire()
    {
        evt(this, EventArgs());
    }
};

class TestSubscriberClass
{
public:
    void OnEventFired(const void* sender, EventArgs args)
    {
        counter += 1;
    }
    int counter = 0;
};

As you see when publisher call fire() counter will increase by one. To subscribe and unsubscribe to event I want to use += and -= operator like in C#. To have some base requirements i wrote three tests:

TEST(when_evt_is_assigned, then_event_should_be_fired_and_subscriber_counter_will_increase)
{
    //Arrnge
    auto pub = new TestPublisherClass();
    auto sub = new TestSubscriberClass();

    pub->evt += std::make_shared<Delegate<EventArgs,TestSubscriberClass>>(sub, &TestSubscriberClass::OnEventFired);
    //Act
    pub->fire();
    //Assert
    ASSERT_EQ(1, sub->counter);
}

TEST(when_evt_is_assigned_to_multiple_subs, then_event_should_be_fired_and_subscribers_counter_will_increase)
{
    //Arrange
    auto pub = new TestPublisherClass();
    auto sub1 = new TestSubscriberClass();
    auto sub2 = new TestSubscriberClass();

    pub->evt += std::make_shared<Delegate<EventArgs,TestSubscriberClass>>(sub1, &TestSubscriberClass::OnEventFired);
    pub->evt += std::make_shared<Delegate<EventArgs,TestSubscriberClass>>(sub2, &TestSubscriberClass::OnEventFired);
    //Act
    pub->fire();
    //Assert
    ASSERT_EQ(1, sub1->counter);
    ASSERT_EQ(1, sub2->counter);
}

TEST(when_not_template_evt_is_assigned_to_multiple_subs_and_unsub_one_sub, then_event_should_be_fired)
{
    //Arrange 
    auto pub = new TestPublisherClass();
    auto sub1 = new TestSubscriberClass();
    auto sub2 = new TestSubscriberClass();

    pub->evt += std::make_shared<Delegate<EventArgs,TestSubscriberClass>>(sub1, &TestSubscriberClass::OnEventFired);
    pub->evt += std::make_shared<Delegate<EventArgs,TestSubscriberClass>>(sub2, &TestSubscriberClass::OnEventFired);
    pub->evt -= std::make_shared<Delegate<EventArgs, TestSubscriberClass>>(sub1, &TestSubscriberClass::OnEventFired);
    //Act
    pub->fire();
    //Assert
    ASSERT_EQ(0, sub1->counter);
    ASSERT_EQ(1, sub2->counter);
}

Originally, implementation use raw pointers, but I try to use smart equivalent. I don't know much about them, but I think my usage not cause any memory leaks. If you see some possibilities of mem leaks write comment and i will try to fix that. At first let's take a look on Delegate<> implementation:

struct EventArgs
{
};

template <class TArgs>
struct DelegateBase
{
  public:
    virtual void invoke(const void *sender, TArgs args) = 0;
};

template <class TArgs, class TSubscriber>
struct Delegate : public DelegateBase<TArgs>
{
  public:
    typedef void (TSubscriber::*HandlerType)(const void *, TArgs);
    typedef std::function<void(const void *, TArgs)> bound_handler;

    Delegate(TSubscriber *subscriber, HandlerType callback)
    {
        _subscriber = subscriber;
        _handler = std::bind(callback, subscriber, std::placeholders::_1, std::placeholders::_2);
    }

    ~Delegate()
    {
        this->_subscriber = nullptr;
    }

    bool equal(std::shared_ptr<Delegate> del) const
    {
        auto addr1 = std::addressof(this->_subscriber);
        auto addr2 = std::addressof(del->_subscriber);
        return *addr1 == *addr2;
    }

    virtual void invoke(const void *sender, TArgs args) override { this->_handler(sender, args); }

  private:
    TSubscriber *_subscriber;
    bound_handler _handler;
};

As you can see my delegate wrap specific member function pointer and call it when invoke method will be called. To have possibility to find specific delegate by subscriber instance. Last element in implementation is Event<> itself:

template <class TArgs>
class Event
{
  public:
    Event() {}
    Event(Event &other) = delete;
    Event(Event &&other) = delete;
    ~Event()
    {
        this->_subscribers.clear();
    }

    void operator()(const void *sender, TArgs args)
    {
        for (auto &subscriber : this->_subscribers)
            subscriber->invoke(sender, args);
    }

    template <class TSubscriber>
    Event &operator+=(std::shared_ptr<Delegate<TArgs, TSubscriber>> handler)
    {
        _subscribers.push_back(handler);
        return *this;
    }

    template <class TSubscriber>
    Event &operator-=(std::shared_ptr<Delegate<TArgs, TSubscriber>> handler)
    {
        auto iterToRemove = std::remove_if(
            std::begin(this->_subscribers),
            std::end(this->_subscribers),
            [&](std::shared_ptr<DelegateBase<TArgs>> del) 
            -> bool { return handler->equal(std::dynamic_pointer_cast<Delegate<TArgs, TSubscriber>> (del)); });
        this->_subscribers.erase(iterToRemove);

        return *this;
    }

  private:
    std::vector<std::shared_ptr<DelegateBase<TArgs>>> _subscribers;
};

I test this implementation a little and it seems to fit my current requirements. What do you think about this events implementation, maybe you have better solution. As always I invite you to comments section. See you !