Обратный вызов функции-члена с помощью libevent - PullRequest
2 голосов
/ 16 октября 2011

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

#include <event.h>

class A
{
    public:
        void eventcb(evutil_socket_t fd, short events, void *ctx) { }
};


static void global_eventcb(evutil_socket_t fd, short events, void *ctx) { }

typedef void (A::*mthd)(evutil_socket_t, short, void*);

int main(void)
{
    struct event_base *evbase = event_base_new();

    mthd eventcb = &A::eventcb;
    A *instance = new A;
    (instance->*eventcb)(NULL, 0, NULL);

    struct event *timer1 = evtimer_new(evbase, global_eventcb, NULL);
    struct event *timer2 = evtimer_new(evbase, (instance->*eventcb), NULL);

    return 0;
}   

Я могу успешно создать указатель метода на eventcb в классе A и вызвать его для экземпляра A (строка 20).

Кроме того, передача глобальной функции (как это было бы в C) в строке 22 также работает нормально.

Однако в строке 23 я пытаюсь передать указатель моего метода на libevent , и при компиляции я получаю следующую ошибку (используя компилятор clang )

example.cpp:23:25: error: no matching function for call to 'event_new'
        struct event *timer2 = evtimer_new(evbase, (instance->*eventcb), NULL);
                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from example.cpp:1:
In file included from /usr/local/include/event.h:71:
/usr/local/include/event2/event.h:749:40: note: instantiated from:
#define evtimer_new(b, cb, arg)        event_new((b), -1, 0, (cb), (arg))
                                       ^~~~~~~~~
/usr/local/include/event2/event.h:833:15: note: candidate function not viable: no know conversion from '<bound member function type>' to 'event_callback_fn'
      (aka 'void (*)(int, short, void *)') for 4th argument
struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
              ^
1 error generated.

Что я делаю не так?

1 Ответ

5 голосов
/ 03 ноября 2011

Указатели на метод экземпляра должны вызываться для экземпляра.Поскольку libevent является библиотекой C, она не предоставляет механизм для непосредственного связывания экземпляра и метода экземпляра, поэтому вам придется делать это самостоятельно.Различные функции создания событий libevent позволяют передавать произвольные данные в качестве аргумента обратного вызова.Указатель экземпляра может быть передан через этот аргумент, либо напрямую, либо упакован в класс с другими аргументами, если обратный вызов принимает дополнительные данные.Обратный вызов события может быть свободной функцией или статическим методом;какой подход выбрать, зависит от ответственности класса (в смысле SOLID, с одинарной ответственностью).

Пример с использованием статического метода и без передачи дополнительных данных:

class A {
public:
    A(struct event_base *);

    bool start_timer();

    static void invoke_timer_handler(evutil_socket_t fd, short events, void *ctx);
    void handle_timeout(evutil_socket_t fd, short events);

protected:
    struct event_base *evbase;
    struct event *timer;
};

A::A(struct event_base *event_base) : evbase(event_base), timer(NULL) {}

bool A::start_timer() {
    // not thread safe.
    if (! timer) {
        timer = evtimer_new(evbase, &A::invoke_timer_handler, this);
        return true;
    }
    return false;
}

void A::invoke_timer_handler(evutil_socket_t fd, short events, void *ctx) {
    (static_cast<A*>(ctx))->handle_timeout(fd, events);
}

void A::handle_timeout(evutil_socket_t fd, short events) {
    ...
    if (evtimer_del(timer)) {
        // error deleting event
        ...
    } else {
        timer=NULL;
    }
}

В примеретак как A::handle_timeout вызывается только из A::invoke_timer_handler, его можно сделать частным или защищенным.

В примере очень простое управление памятью.В общем случае код должен обеспечивать наличие экземпляра (и других аргументов обратного вызова, если аргумент обратного вызова не просто A*) в течение всего времени существования события, чтобы предотвратить ошибки доступа.Также следует убедиться, что экземпляр не просочился, когда событие больше не требуетсяЕсли экземпляр владеет событием, управление памятью является относительно простым.Параллельность может также добавить усложнение, которое влияет на управление памятью.

Существующие реализации на уровне кода анонимных функций (например, boost :: lambda) и предстоящие лямбда-выражения из C ++ 11 полагаются на оператор вызова функции (operator()), который не поддерживается в простом C. Таким образом, анонимные функции не подходят для использования в качестве обратных вызовов libevent или любых других обратных вызовов библиотеки C.

...