Я написал систему событий. Есть несколько случаев, когда это не работает, и я просто не могу найти способ заставить их работать. Он отлично работает для любой не лямбда-функции, не являющейся членом, то есть только для одной свободной функции.
Я бы хотел, чтобы все отлично работало.
В настоящее время только одна функция с любой идентичной подписью может быть подписана, несколько было бы неплохо. Лямбда считает "разными" сигнатурами, поэтому вы можете иметь свободную функцию и лямбду, подписанную как, но лямбда не может быть отписана, потому что 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