使事件系统与成员函数一起使用

时间:2018-06-24 18:42:48

标签: c++ events callback c++17

我写了一个事件系统。在某些情况下它不起作用,我似乎无法找到一种使它们起作用的方法。它适用于任何一个非lambda,非成员函数,即只有一个自由函数。

我希望它在任何情况下都能很好地工作。

当前只能订阅任何相同签名的功能,多个可以很好。 Lambda算作“不同”签名,因此您可以拥有一个免费功能,并且一个lambda都可以订阅,但是不能取消订阅该lambda,因为target_types与预期的不同。

刚结束的成员函数无法编译,因为签名void __thiscall ClassName::*FuncName([signature])不会转换为std::function<void([signature])>

确保您的编译器设置为启用C ++ 17功能

Event.hpp

#pragma once

#include <algorithm>
#include <functional>
#include <tuple>
#include <utility>
#include <vector>

template<typename... Args>
class Event {
public:
    using cb_t = std::function<void(Args...)>;
    struct event_t {
        cb_t cb{};
        std::tuple<Args...> args;
        event_t(const cb_t& cb, Args... args) : cb(cb), args(std::forward_as_tuple<Args...>(std::decay_t<Args>(args)...)) { }
        bool operator==(const event_t& rhs) {
            //Test pointer then signature
            return (this->cb.target_type().hash_code() == rhs.cb.target_type().hash_code())
                || (this->args == rhs.args);
        }
    };
    Event() = default;
    ~Event() = default;

    void Subscribe(const cb_t& cb, Args... args) {
        event_t sub(cb, args...);
        Subscribe(sub);
    }

    void Unsubscribe(const cb_t& cb) {
        _subscribers.erase(
            std::remove_if(
                std::begin(_subscribers),
                std::end(_subscribers),
                [&cb](const event_t& e) {
                    return (cb.target_type().hash_code() == e.cb.target_type().hash_code());
                }),
            std::end(_subscribers));
    }

    void Trigger() const {
        for(auto& s : _subscribers) {
            std::apply(s.cb, s.args); //REQUIRES C++17
        }
    }

protected:
private:
    std::vector<event_t> _subscribers{};

    void Subscribe(const event_t& cb) {
        auto found = std::find(std::begin(_subscribers), std::end(_subscribers), cb);
        if(found == std::end(_subscribers)) {
            _subscribers.push_back(cb);
        }
    }

    void Unsubscribe(const event_t& cb) {
        auto found = std::find(std::begin(_subscribers), std::end(_subscribers), cb);
        if(found != std::end(_subscribers)) {
            std::iter_swap(found, _subscribers.end() - 1);
            _subscribers.pop_back();
        }
    }

};

//Specialization to get Event<void> to work
template<>
class Event<void> : public Event<> { };

示例用法:

#include "Event.hpp"
#include <iostream>
#include <string>

Event<> OnThing1; //Event signature takes no arguments
Event<void> OnThing2; //Ditto
Event<std::string> OnThing3; //Event signature takes a std::string
Event<std::string, int> OnThing4; //Event signature takes a std::string and an int

//...

void MyCallbackFunction1() { std::cout << "Hi from MCF1!\n"; }
void MyCallbackFunction2() { std::cout << "Hi from MCF2!\n"; }
void MyCallbackFunction3(const std::string& str) { std::cout << "MCF3 says " << str << "!\n"; }
void MyCallbackFunction4(const std::string& str, int i) { std::cout << "MCF4 says " << str << " and " << i << "!\n"; }

int main() {

    OnThing1.Subscribe(MyCallbackFunction1);
    OnThing1.Subscribe(MyCallbackFunction2); //Uh-oh! Same signature as MCF1, does not get subscribed!
    OnThing2.Subscribe(MyCallbackFunction2); //Okay because it's on a different Event
    OnThing3.Subscribe(MyCallbackFunction3, std::string("Hello World"));
    //A lambda can be Subscribed because the internal representation of the signature is not the same.
    OnThing3.Subscribe([](const std::string& str){ std::cout << str << '\n'; }, std::string("Hello Lambda"));
    OnThing4.Subscribe(MyCallbackFunction4, std::string("Hello World"), 1);

    //...

    OnThing1.Trigger(); //Calls MCF1 but not MCF2
    OnThing2.Trigger(); //Calls MCF2
    OnThing3.Trigger(); //Calls MCF3 and the lambda
    OnThing4.Trigger(); //Calls MCF4

    //...

    OnThing1.Unsubscribe(MyCallbackFunction1);
    OnThing1.Unsubscribe(MyCallbackFunction2); //MCF2 was never registered in the first place.
    OnThing2.Unsubscribe(MyCallbackFunction2);
    OnThing3.Unsubscribe(MyCallbackFunction3);
    //Lambda can not be unsubscribed because there is no way to get the type information. Re-creating the lambda will result in a different signature.
    OnThing4.Unsubscribe(MyCallbackFunction4);
}

输出:

Hi from MCF1!
Hi from MCF2!
MCF3 says Hello World!
Hello Lambda
MCF4 says Hello World and 1

0 个答案:

没有答案