Сложенные люди.
Я пытаюсь реализовать шаблон наблюдателя (esque?) Для моей программы. У меня есть компонент, который хранит, какие функции должны быть вызваны, если происходит событие. Моя проблема в том, что я не знаю, как мне удалить свою функцию из контейнера, если возникнет такая необходимость. Попытка сохранить функции по ссылке, но я не уверен, как это сделать (или, если это возможно.)
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
enum class EVENT_TYPE{
anEvent
};
class EventableComponent{
map<EVENT_TYPE, vector<function<void()>>> listeners;
public:
void trigger(EVENT_TYPE _et){
for(auto& it : listeners[_et]){
it();
}
}
void registerListener(EVENT_TYPE _et, function<void()> _fn){
listeners[_et].push_back(_fn);
};
void removeListener(EVENT_TYPE _et, function<void()> _fn){
//error C2678: binary '==' : no operator found which takes a left-hand operand of type 'std::function<void (void)>'
//(or there is no acceptable conversion)
listeners[_et].erase(remove(listeners[_et].begin(), listeners[_et].end(), _fn), listeners[_et].end());
};
};
int main(){
EventableComponent ec;
// this would become a member function for a class somewhere down the line
auto fn = [](){cout << "Hello.\n"; };
ec.registerListener(EVENT_TYPE::anEvent, fn);
ec.trigger(EVENT_TYPE::anEvent);
ec.removeListener(EVENT_TYPE::anEvent, fn);
ec.trigger(EVENT_TYPE::anEvent);
cin.get();
return 0;
};
Ваша проблема может быть сведена к тому, что два std::function
экземпляры нельзя сравнивать на равенство. std::remove
требует operator==
, а также std::function
не имеет его Увидеть «Почему std :: function не сравнимо по равенству?».
Рассмотрим следующую ситуацию.
Допустим, вы определили две лямбды в вашем main
:
auto fn = [](){cout << "Hello.\n"; };
auto fn2 = [](){cout << "Hello.\n"; };
Теперь, эти два равны или нет? Они делают то же самое, но, возможно, это чистое совпадение. Станут ли они неравными, если второй "Hello"
стал "Hello2"
? Станут ли они неравными, если второй не будет больше лямбда, а реальная функция void f()
?
Дело в том, что не может быть в общем-то полезное определение равенства для функциональных объектов, так что вам решать, что на самом деле означает равенство в контексте ваш программа.
У вас есть несколько вариантов решения проблемы. Один будет работать на указатели в std::function
объекты. Указатели можно сравнивать и правильно использовать std::unique_ptr
гарантирует, что освобождение обрабатывается правильно.
Или вы назначаете идентификатор каждому std::function
ты используешь. Смотрите следующий модифицированный пример вашего кода, в котором прямое хранение std::function<void()>
в векторе заменяется пользовательским типом EventFunction
который отображает int
к объекту функции. В примере используются std::remove_if
сравнивать только int
s:
#include <map>
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
enum class EVENT_TYPE{
anEvent
};
struct EventFunction {
function<void()> f;
int id;
};
class EventableComponent{
map<EVENT_TYPE, vector<EventFunction>> listeners;
public:
void trigger(EVENT_TYPE _et){
for(auto& it : listeners[_et]){
it.f();
}
}
void registerListener(EVENT_TYPE _et, EventFunction _fn){
listeners[_et].push_back(_fn);
};
void removeListener(EVENT_TYPE _et, int function_id){
//error C2678: binary '==' : no operator found which takes a left-hand operand of type 'std::function<void (void)>'
//(or there is no acceptable conversion)
listeners[_et].erase(remove_if(listeners[_et].begin(), listeners[_et].end(),
[&](EventFunction const& e) { return e.id == function_id; }), listeners[_et].end());
};
};
int main(){
EventableComponent ec;
// this would become a member function for a class somewhere down the line
auto fn = [](){cout << "Hello.\n"; };
ec.registerListener(EVENT_TYPE::anEvent, EventFunction{ fn, 1 });
ec.trigger(EVENT_TYPE::anEvent);
ec.removeListener(EVENT_TYPE::anEvent, 1);
ec.trigger(EVENT_TYPE::anEvent);
};
Пробовал хранить функции по ссылке, но я не уверен, как это сделать
это (или если это возможно.)
Это невозможно, потому что вы не можете хранить ссылки в контейнерах стандартной библиотеки. Но я полагаю, что идея похожа на ту, что содержит указатели, о которых я упоминал выше.
Других решений пока нет …