Event Callback Daemon - PullRequest
       13

Event Callback Daemon

2 голосов
/ 09 сентября 2011

Я работаю над демоном событий в C ++, в котором я хотел бы использовать обратные вызовы функций-членов. В основном очередь событий собирает события, которые демон постоянно обслуживает. Существует структура класса базового класса с идентификатором, и все события будут производными от него. Я хотел бы, чтобы методы, зарегистрированные для каждого события, использовали производный тип события в своей подписи.

struct Event
{
    unsigned int eventId;
};

struct EventA : public Event
{
    unsigned int x;
    unsigned int y;
};

// and struct EventB, EventC (use your imagination...)

const unsigned int EVENT_A = 1;
const unsigned int EVENT_B = 2;
const unsigned int EVENT_C = 3;

class Foo
{
public:
    void handlerMethod_A(const EventA& e);
    void handlerMethod_B(const EventB& e);
};

class Bar
{
public:
    void handlerMethod_C(const EventC& e);
};

Тогда демон разрешит этим классам подписывать свои функции-члены, используя указатель this.

class EventDaemon
{
public:

    void serviceEvents();

    template <class CallbackClass, class EventType>
    void subscribe(
        const unsigned int eventId,
        CallbackClass* classInstancePtr,
        void (CallbackClass::*funcPtr)(EventType));

private:
    Queue<Event*> eventQueue_;
};

Так что вне этого класса вы можете сделать что-то вроде:

EventDaemon* ed = new EventDaemon();
Foo* foo = new Foo();
Bar* bar = new Bar();

ed->subscribe(EVENT_A, foo, Foo::handlerMethod_A);
ed->subscribe(EVENT_B, foo, Foo::handlerMethod_B);
ed->subscribe(EVENT_C, bar, Bar::handlerMethod_C);

И цикл EventDaemon будет выглядеть так:

void EventDaemon::serviceEvents()
{
    while (true)
    {
        if (eventQueue_.empty())
        {
            // yield to other threads
        }
        else
        {
            // pop an event out of the FIFO queue
            Event e* = eventQueue_.pop();
            // somehow look up the callback info and use it
            classInstancePtr->*funcPtr(reinterpret_cast<?*>(e));
        }
    }
}

Так что мой вопрос в том, как я могу хранить указатели «this» и указатели на функции-члены в некотором виде массива по идентификатору события. Таким образом, я мог бы искать «classInstancePtr» и «funcPtr», используя e-> eventId и тип события, а также для переинтерпретации приведения.

Ответы [ 3 ]

3 голосов
/ 09 сентября 2011

Вы слишком усердно работаете.Используйте функции повышения:

http://www.boost.org/doc/libs/1_47_0/doc/html/function.html

Они работают независимо от того, есть у вас объект или нет.Они увеличат ваше время компиляции.

Обратите внимание: всякий раз, когда вы сталкиваетесь с этими типами вопросов, когда вы знаете, что у многих людей должна быть одна и та же проблема, возможно, существует простой вариант, и, если он не входит в стандартв библиотеке, это, вероятно, в boost.

В ответ на Ника я постоянно добавляю объекты функции boost в векторы и еще много чего.

Я обнаружил, что, хотя объекты функции boost могут удерживатьссылки на объекты, если они делают это, могут привести к ошибкам с продолжительностью жизни объектов, и лучше, чтобы они содержали копии объектов класса (вы сталкиваетесь с теми же ошибками, однако вы пытаетесь удерживать ссылку на экземпляр объекта, который вы не делаетеобязательно контролируйте время жизни).Шаблон:

class Foo
{
  struct Member
  {
     // member variable definitions
  };
  shared_ptr<Member> m_; // the only real member variable
public:
  // etc. including the all-important copy
  // constructor and assignment operator and
  // don't forget the member function that gets stuck into
  // the boost function as a callback!
};

, где все переменные-члены хранятся в shared_ptr, обеспечивает хорошую производительность, и вам не нужно беспокоиться о временах жизни объектов, содержащихся в объектах функций, поскольку вы можете копировать их по значению.Для многопоточного кода (который я всегда пишу сейчас) нужны дополнительные вещи, такие как, по крайней мере, один элемент-буст-мьютекс в Member или какой-то другой способ, чтобы гарантировать, что значения не будут вытеснены.

1 голос
/ 10 сентября 2011

boost::function [или, если ваша система поддерживает это, std::function] позаботится о том, чтобы указатель this удерживался достаточно хорошо, с дополнительным преимуществом - не требовать реального объекта, если он не нужен.Таким образом, вместо void (SomeType::*)(EventA) у вас есть std::function<void(EventA)>, и вы вызываете std::bind в зависимости от ситуации.

subscribe(EVENT_A, std::bind(&foo::handleEventA, &foo, std::placeholders::_1));

Тривиальная функция-обертка может использоваться для обеспечения той же подписи, что вы изначально предложили, и скрытия неприятногозаполнители.

Конечно, у вас все еще есть проблема, связанная с тем, что каждый тип события имеет свою собственную подпись, и вам необходимо убедиться, что вы используете правильный код идентификатора события.В обоих случаях ваш базовый тип события может помочь.Ваш обратный вызов не должен принимать EventA&;он может принять Event& и dynamic_cast до EventA во время выполнения.Для идентификатора запросите тип напрямую.

struct Event {
  virtual void ~Event() { }
  virtual int ID() =0;
};

template<typename E>
struct EventHelper : Event {
  virtual int ID() { return E::EventID; }
};

struct EventA : EventHelper<EventA> {
    static const int EventID = 89;
};

Теперь, если у вас есть объект Event* [когда вы отправляете свои события], вы можете сделать p->ID(), чтобы получить соответствующий идентификатор,и если у вас есть тип EventA [при регистрации ваших обратных вызовов], вы можете сделать EventA::EventID.

Итак, теперь все, что вам нужно сохранить, - это std::function<void(const Event&)> и соответствующее значение int для каждого из ваших обратных вызовов, независимо от того, какой у вас фактический тип события.

void subscribe(int id, std::function<void(const Event&)> f) {
    callbacks.insert(std::make_pair(id, f));
}

template<typename E>
void subscribe(std::function<void(const Event&)> f) {
   subscribe(E::EventID, f);
}

template<typename O, typename E>
void subscribe(O* p, void (O::*f)(const Event&)) {
   subscribe<E>(std::bind(f, p, std::placeholders::_1));
}

По-прежнему существует проблема, связанная с тем, что ошибка пользователя при подписке может привести к неправильному вызову функции.Если вы правильно использовали dynamic_cast в обратном вызове, это будет обнаружено во время выполнения, но проверка во время компиляции была бы хорошей.Так что, если мы автоматизируем это dynamic_cast?Для этого шага я собираюсь использовать лямбда-выражения c ++ 11, но она может быть реализована и в C ++ 03 с использованием различных методов.

template <class CallbackClass, class EventType>
void subscribe(CallbackClass* classInstancePtr, void (CallbackClass::*funcPtr)(EventType)) {
    subscribe<EventType::EventID>([&](const Event& e) { 
       (classInstancePtr->*funcPtr)(dynamic_cast<const EventType&>(e));
    });
}

Итак, мы завершили работу полностьюВернитесь к исходному интерфейсу, где ваши обратные вызовы принимают фактический тип, над которым они будут работать, но внутренне вы втиснули их в общую подпись.

0 голосов
/ 13 сентября 2011

Хорошо, я закончил реализацию моего первоначального желаемого интерфейса.Я просматривал ответ Денниса, но в конечном итоге получил функторы, и я понял, что то, что я искал, было простым полиморфным решением.До этого я не мог понять, что смог создать базовый класс без шаблонов, чтобы использовать его для хранения шаблонных классов в векторах / массивах.Я думаю, что это то, что Мейман пытался сказать мне ... поэтому я прошу прощения, что не сразу понял.Просто чтобы прояснить, хотя я действительно искал решение для реализации для моей собственной выгоды и знаний, а не просто стороннюю библиотеку для выполнения работы.Так что я думаю, я бы искал как функции Boost работают, а не только то, что они существуют и являются удивительными.

Если кому-то все еще интересно здесь, есть важные части того, с чем я закончил(за исключением некоторых посторонних вещей и проверки ошибок):

  • EventFunctor в основном является указателем на класс шаблона функции-члена
  • EventFunctorBase - это базовый класс без шаблонов, используемый для хранения их в векторе
  • Событие является динамическим приведением с использованием шаблонного типа перед использованием для вызова обратного вызова
class EventDaemon
{
public:

    template <class CallbackClass, class EventType>
    void subscribe(
        const EventId eventId,
        CallbackClass* callbackClassInstancePtr,
        void (CallbackClass::*funcPtr)(const EventType&));

private:
    EventFunctorBase* callbacks_[MAX_NUM_EVENTS];
};

template <class CallbackClass, class EventType>
void EventDaemon::subscribe(
    const EventId eventId,
    CallbackClass* callbackClassInstancePtr,
    void (CallbackClass::*funcPtr)(const EventType&))
{
    callbacks_[eventId] = new EventFunctor<CallbackClass,EventType>(callbackClassInstancePtr,funcPtr);
}
class EventFunctorBase
{
public:
    EventFunctorBase();
    virtual ~EventFunctorBase();
    virtual void operator()(const Event& e)=0;
};
template <class CallbackClass, class EventType>
class EventFunctor : public EventFunctorBase
{
public:

    EventFunctor(
        CallbackClass* callbackClassInstancePtr,
        void (CallbackClass::*funcPtr)(const EventType&));

    virtual void operator()(const Event& e);

private:
    CallbackClass* callbackClassInstancePtr_;
    void (CallbackClass::*funcPtr_)(const EventType&);
};

template <class CallbackClass, class EventType>
EventFunctor<CallbackClass,EventType>::EventFunctor(
    CallbackClass* callbackClassInstancePtr,
    void (CallbackClass::*funcPtr)(const EventType&))
    :
    callbackClassInstancePtr_(callbackClassInstancePtr),
    funcPtr_(funcPtr)
{
}

template <class CallbackClass, class EventType>
/*virtual*/ void EventFunctor<CallbackClass,EventType>::operator()(const Event& e)
{
    (callbackClassInstancePtr_->*funcPtr_)(dynamic_cast<const EventType&>(e));
}

EventDaemon loop

while (true_)
{
    if (eventQueue_->empty())
    {
        // yield to other threads
    }
    else
    {
        Event* e = eventQueue_.pop();
        (*(callbacks_[e->ID]))(*e);
    }
}

Последними моими шагами здесь будет попытка устранить необходимость в том, чтобы разработчик определял идентификатор для каждого события ... конечно, это может закончиться новым постом позже на этой неделе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...