2

我正在尝试将一个成员函数传递给应该被视为回调的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;
}   

我可以成功地在 A 类中创建一个指向eventcb的方法指针,并在 A 的实例上调用它(第 20 行)。

此外,在第 22 行传递一个全局函数(就像在 C 中所做的那样)也可以正常工作。

但是,在第 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.

我究竟做错了什么?

4

1 回答 1

5

实例方法指针需要一个实例才能被调用。由于 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 的即将到来的 lambda 表达式依赖于函数调用运算符 ( operator()),这在纯 C 中不受支持。因此匿名函数不适合用作 libevent回调或任何其他 C 库回调。

于 2011-11-03T02:17:07.250 回答