Класс события нуждается в обновлении - PullRequest
0 голосов
/ 19 ноября 2018

У меня есть класс Event, который написан наполовину C и наполовину C ++ 11.

В настоящее время он не работает ни с лямбдами, ни с std::functions, только с свободными функциями или функциями-членами.

Это очень трудно использовать должным образом (я никогда не получал простой метод Subscribe для компиляции при использовании), и использование void* и указателей на необработанные функции просто брутто.

Я бы хотелобновите его в терминах C ++ 17 с правильными типами шаблонов, работающими с лямбдами и std::function, и, надеюсь, только с одним открытым набором методов подписки / отмены подписки, которые просто работают со всем, что я ему даю.

Event.hpp

#pragma once

#include <vector>

template <typename... ARGS>
class Event {
public:
    struct event_sub_t;
    using cb_t = void(*)(event_sub_t*, ARGS...);
    using cb_with_arg_t = void(*)(void*, ARGS...);

    struct event_sub_t {
        cb_t cb;
        void *secondary_cb;
        void *user_arg;
    };

    Event() = default;
    ~Event() = default;

    void Subscribe(void *user_arg, cb_with_arg_t cb) {
        event_sub_t sub;
        sub.cb = FunctionWithArgumentCallback;
        sub.secondary_cb = cb;
        sub.user_arg = user_arg;
        subscriptions.push_back(sub);
    }

    void Unsubscribe(void *user_arg, void* cb) {
        subscriptions.erase(std::remove_if(std::begin(subscriptions),
                                           std::end(subscriptions),
                                           [&cb, &user_arg](const event_sub_t& sub) {
                                               return (sub.secondary_cb == cb) && (sub.user_arg == user_arg);
                                           }),
                            std::end(subscriptions));
    }

    void Unsubscribe_by_argument(void *user_arg) {
        subscriptions.erase(std::remove_if(std::begin(subscriptions),
                                           std::end(subscriptions),
                                           [&user_arg](const event_sub_t& sub) {
                                               return sub.user_arg == user_arg;
                                           }),
                            std::end(subscriptions));
    }

    template <typename T>
    void Subscribe_method(T *obj, void (T::*mcb)(ARGS...)) {
        event_sub_t sub;
        sub.cb = MethodCallback<T, decltype(mcb)>;
        sub.secondary_cb = *(void**)(&mcb);
        sub.user_arg = obj;
        subscriptions.push_back(sub);
    }

    template <typename T>
    void Unsubscribe_method(T *obj, void (T::*mcb)(ARGS...)) {
        Unsubscribe(obj, *(void**)&mcb);
    }

    template <typename T>
    void Unsubscribe_object(T *obj) {
        Unsubscribe_by_argument(obj);
    }

    void Trigger(ARGS... args) {
        for(auto& sub : subscriptions) {
            sub.cb(&sub, std::forward<ARGS>(args)...);
        }
    }

private:
    std::vector<event_sub_t> subscriptions;

    static void FunctionWithArgumentCallback(event_sub_t *sub, ARGS... args);

    template <typename T, typename MCB>
    static void MethodCallback(event_sub_t *sub, ARGS... args);

};

template <typename ...ARGS>
void Event<ARGS...>::FunctionWithArgumentCallback(event_sub_t *sub, ARGS... args) {
    cb_with_arg_t cb = (cb_with_arg_t)(sub->secondary_cb);
    cb(sub->user_arg, std::forward<ARGS>(args)...);
}

template <typename ...ARGS>
template <typename T, typename MCB>
void Event<ARGS...>::MethodCallback(event_sub_t *sub, ARGS... args) {
    MCB mcb = *(MCB*)&(sub->secondary_cb);
    T *obj = (T*)(sub->user_arg);
    (obj->*mcb)(std::forward<ARGS>(args)...);
}

Текущее использование:

class Foo {
    public:
        //...
        void Update() { OnEventFoo.Trigger(text); }
        Event<const std::string&> OnEventFoo{};
    private:
        std::string text{};
};

//Foo::Update is called somewhere in other code... 

//Bar subscribes/unsubscribes to Foo's event.
//Doesn't have to be RAII, can be as simple as putting
//the subscribe/unsubscribe calls before and after some other function call.
class Bar {
    public:
    std::string text{};
    explicit Bar(Foo& foo)
    : _foo(foo)
    {
        foo.OnEventFoo.Subscribe_method(this, &Bar::Thing2);
    }
    ~Bar() {
        foo.OnEventFoo.Unsubscribe_method(this, &Bar::Thing2);
    }
    void Thing2(const std::string& text) {
        std::cout << "Calling " << __FUNCTION__ << " with " << text;
    }
    private:
        Foo _foo{};
};

Использование по назначению:

//...Foo and Bar classes and stuff
static auto bar_lambda = [bar](const std::string& text){ bar.Thing2(text) };
foo.Subscribe(bar_lambda, "Hello Bar!");
foo.Subscribe(Bar::Thing2, bar.text);
foo.Subscribe(FreeOrStdFunction, "Free Bar!");
//...
foo.Unsubscribe(Bar::Thing2);
foo.Unsubscribe(FreeFunction);
foo.Unsubscribe(bar_lambda);

1 Ответ

0 голосов
/ 19 ноября 2018

Не обязательно понять, что вам нужно.

Но мне кажется, что вам нужно std::bind().

В любом случае ... если аргументы для одного вызываемого объекта передаются в Subscribe(), мне кажется, что Event больше не должен быть классом шаблона и что std::vector из std::function является чем-то следующим образом

private:
   std::vector<std::function<void()>> subsV;

Я имею в виду: вектор std::function типа void().

Вы можете заполнить его с помощью следующего метода

  template <typename F, typename ... Args>
  std::size_t Subscribe (F const & f, Args const & ... as)
   { 
     subsV.emplace_back(std::bind(f, as...));

     return subsV.size() - 1u;
   }

Заметьте, что с простым вызываемым (не нестатическим методом класса / структуры) вы должны вызывать его, передавая сначала вызываемый, а затем аргументы

   auto i1 = e.Subscribe(
      [](int, long){ std::cout << "l1" << std::endl; }, 0, 1l);

но, вызывая его нестатическим методом, вы должны сначала передать указатель на метод, затем объект или указатель на объект (работает в обоих случаях) класса и, наконец, аргументы для метода.

foo    f;

// ...............................V   works with objects
auto i2 = e.Subscribe(&foo::func, f, "string 1"); 
auto i3 = e.Subscribe(&foo::funv, &f, "string 2");
// ...............................^^  and works with pointers

Для Unsuscribe() я предлагаю передать индекс подписки (возвращается Subscribe()

  void Unsubscribe (std::size_t idx)
   { subsV.at(idx) = nullptr; }

и Trigger() просто становятся

  void Trigger ()
   {
     for ( auto & sub : subsV )
        if ( sub )
           sub();
   }

Ниже приведен полный пример компиляции (должен работать также с C ++ 11)

#include <vector>
#include <iostream>
#include <functional>

class Event
 {
   private:
      std::vector<std::function<void()>> subsV;

   public:

      Event() = default;
      ~Event() = default;

      template <typename F, typename ... Args>
      std::size_t Subscribe (F const & f, Args const & ... as)
       { 
         subsV.emplace_back(std::bind(f, as...));

         return subsV.size() - 1u;
       }

      void Unsubscribe (std::size_t idx)
       { subsV.at(idx) = nullptr; }

      void Trigger ()
       {
         for ( auto & sub : subsV )
            if ( sub )
               sub();
       }
 };

struct foo 
 {
   void func (std::string const & s)
    { std::cout << "foo::func(): " << s << std::endl; }
 };

int main()
 {
   Event  e;
   foo    f;

   auto i1 = e.Subscribe(
      [](int, long){ std::cout << "l1" << std::endl; }, 0, 1l);
   auto i2 = e.Subscribe(&foo::func, f, "string 1");
   auto i3 = e.Subscribe(&foo::func, &f, "string 2");

   e.Trigger();

   e.Unsubscribe(i2);

   e.Trigger();

   e.Unsubscribe(i1);

   e.Trigger();

   e.Unsubscribe(i3);

   e.Trigger();
 }
...