Я пытался реализовать систему событий, подобную C#, на C++ с шаблонами функций tr1, используемыми для хранения функции, обрабатывающей событие.
Я создал вектор, чтобы к этому событию можно было подключить несколько слушателей, то есть:
vector< function<void (int)> > listenerList;
Я хотел бы иметь возможность удалить обработчик из списка, чтобы остановить прием событий слушателем.
Итак, как мне найти в этом списке запись, соответствующую данному слушателю? Могу ли я проверить, относится ли объект «функция» в списке к определенной функции?
Спасибо!
Обновлено: изучив подход boost :: signal, кажется, что он, вероятно, реализован с использованием системы токенов, как некоторые из вас предложили. Вот некоторая информация об этом. Наблюдатель сохраняет объект «Соединение», когда он присоединяется к событию, и этот объект соединения используется для разъединения при необходимости. Таким образом, похоже, что независимо от того, используете ли вы Boost или самостоятельно с tr1, основной принцип один и тот же. т.е. будет немного коряво :)
FAQ # 1 в документации по функциям ускорения, кажется, отвечает на ваш вопрос - и простой ответ - «нет».
предложение (раздел IIIb.) Заявляет, что они не будут ни в коем случае сопоставимы. Если вы прикрепите к ним дополнительную информацию, вы легко сможете идентифицировать каждый обратный вызов. Например, если вы просто определяете структуру, обертывающую указатель функции, вы можете удалить их (при условии, что у вас есть та же структура, которую вы вставили). Вы также можете добавить несколько полей в структуру (например, автоматически сгенерированный идентификатор, за который клиент может удерживать) и сравнить с этим.
Функторы нельзя хранить как хеш-значения. С ними ничего не поделать. Обертывание их в структуру (даже одноэлементную структуру, состоящую только из функтора) позволит вам делать с ними все, что угодно. Умышленно, что их нельзя сравнивать / хешировать / т. Д. Без некоторой работы.
Если вы сохраняете только указатели на функции (а не другие функторы, соответствующие требуемой сигнатуре), это легко (см. Код ниже). Но в целом ответ, как сказано на других плакатах, отрицательный. В этом случае вы, вероятно, захотите сохранить свои функторы в хэше в качестве значений, а ключи - это то, что пользователь предоставляет при добавлении и удалении.
В приведенном ниже коде показано, как получить вызываемый объект-функтор / указатель. Чтобы использовать его, вы должны знать точный тип извлекаемого объекта (т.е. typeid
указанного вами типа должен соответствовать typeid
содержащегося в нем функтора / указателя).
#include <cstdio>
#include <functional>
using std::printf;
using std::tr1::function;
int main(int, char**);
static function<int (int, char**)> main_func(&main);
int
main(int argc, char** argv)
{
printf("%p == %p\n", *main_func.target<int (*)(int, char**)>(), &main);
return 0;
}
Как насчет
map<key-type, function<void (int)> > listeners;
Я думаю, что хеш (std :: tr1 :: unordered_map) - лучший выбор для такого рода вещей. Если только не важно вызывать слушателей в порядке регистрации, в этом случае тип ключа должен включать / быть серийным номером.
Хорошо, ты заставил меня работать. Самая сложная часть - это попытка соответствовать точному шаблону использования событий C#. Если вы пропустите это, есть НАМНОГО более простых способов сделать то, о чем вы просите. (Мой коллега Джейсон повсюду использует объект Notifier.) В любом случае, вот невероятно скучный код, который делает то, что вы хотите. К сожалению, он не позволяет передавать параметры от субъекта к наблюдателю. Для этого вам нужно добавить еще больше умов.
#include "stdafx.h"
#include <iostream>
#include <string>
#include <list>
#include <algorithm>
#include <boost/tr1/functional.hpp>
#include <boost/tr1/memory.hpp>
using namespace std;
using namespace std::tr1;
template <typename T>
class ObserverHandle
{
public:
typedef boost::function<void (T*)> const UnderlyingFunction;
ObserverHandle(UnderlyingFunction underlying)
: _underlying(new UnderlyingFunction(underlying))
{
}
void operator()(T* data) const
{
(*_underlying)(data);
}
bool operator==(ObserverHandle<T> const& other) const
{
return (other._underlying == _underlying);
}
private:
shared_ptr<UnderlyingFunction> const _underlying;
};
class BaseDelegate
{
public:
virtual bool operator==(BaseDelegate const& other)
{
return false;
}
virtual void operator() () const = 0;
};
template <typename T>
class Delegate : public BaseDelegate
{
public:
Delegate(T* observer, ObserverHandle<T> handle)
: _observer(observer),
_handle(handle)
{
}
virtual bool operator==(BaseDelegate const& other)
{
BaseDelegate const * otherPtr = &other;
Delegate<T> const * otherDT = dynamic_cast<Delegate<T> const *>(otherPtr);
return ((otherDT) &&
(otherDT->_observer == _observer) &&
(otherDT->_handle == _handle));
}
virtual void operator() () const
{
_handle(_observer);
}
private:
T* _observer;
ObserverHandle<T> _handle;
};
class Event
{
public:
template <typename T>
void add(T* observer, ObserverHandle<T> handle)
{
_observers.push_back(shared_ptr<BaseDelegate>(new Delegate<T>(observer, handle)));
}
template <typename T>
void remove(T* observer, ObserverHandle<T> handle)
{
// I should be able to come up with a bind2nd(equals(dereference(_1))) kind of thing, but I can't figure it out now
Observers::iterator it = find_if (_observers.begin(), _observers.end(), Compare(Delegate<T>(observer, handle)));
if (it != _observers.end())
{
_observers.erase(it);
}
}
void operator()() const
{
for (Observers::const_iterator it = _observers.begin();
it != _observers.end();
++it)
{
(*(*it))();
}
}
private:
typedef list<shared_ptr<BaseDelegate>> Observers;
Observers _observers;
class Compare
{
public:
Compare(BaseDelegate const& other)
: _other(other)
{
}
bool operator() (shared_ptr<BaseDelegate> const& other) const
{
return (*other) == _other;
}
private:
BaseDelegate const& _other;
};
};
// Example usage:
class SubjectA
{
public:
Event event;
void do_event()
{
cout << "doing event" << endl;
event();
cout << "done" << endl;
}
};
class ObserverA
{
public:
void test(SubjectA& subject)
{
subject.do_event();
cout << endl;
subject.event.add(this, _observe);
subject.do_event();
subject.event.remove(this, _observe);
cout << endl;
subject.do_event();
cout << endl;
subject.event.add(this, _observe);
subject.event.add(this, _observe);
subject.do_event();
subject.event.remove(this, _observe);
subject.do_event();
subject.event.remove(this, _observe);
cout << endl;
}
void observe()
{
cout << "..observed!" << endl;
}
private:
static ObserverHandle<ObserverA> _observe;
};
// Here's the trick: make a static object for each method you might want to turn into a Delegate
ObserverHandle<ObserverA> ObserverA::_observe(boost::bind(&ObserverA::observe, _1));
int _tmain(int argc, _TCHAR* argv[])
{
SubjectA sa;
ObserverA oa;
oa.test(sa);
return 0;
}
И вот результат:
doing event
donedoing event
..observed!
donedoing event
donedoing event
..observed!
..observed!
done
doing event
..observed!
done
Я не знаю, заблокированы ли вы в std C++ и tr1, но если нет, похоже, что вашей проблемы можно было бы полностью избежать, если бы вы просто использовали что-то вроде boost :: signal и boost :: bind для решения вашей проблемы. исходная проблема - создание системы событий - вместо того, чтобы пытаться свернуть свою собственную.
Да, важно, так сказать, увидеть лес, несмотря на деревья. Спасибо, что написали ответ, о котором я хотел бы отступить назад, чтобы подумать!
У меня была похожая проблема, и я нашел решение. Я использовал некоторые функции C++ 0x, но только для удобства, они не являются важной частью. Взгляните сюда:
> Система обмена сообщениями: обратные вызовы могут быть любыми
Если вы собираетесь использовать структуру с дополнительной информацией, вы также можете сохранить функторы как хеш-значения, а GUID / что-то еще как хеш-ключи. :-)