C ++中的C风格回调11

时间:2014-08-26 19:05:45

标签: c++ c++11 curl lambda callback

在C ++ 11项目中,我使用的是C风格的第三方库(在我的情况下为curl),需要C风格的回调。

为了达到这个目的,我使用了“poiner-to-member”运算符:

size_t c_callback_wrapper(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    MyClass *p = (MyClass*)userdata;
    return (p->*&MyClass::actualCallback)(ptr, size, nmemb, userdata);
}


void Myclass::performSomething() {
    // register callback function
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, c_callback_wrapper);
    // register userdata
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
    ...
    res = curl_easy_perform(curl);
}

这个解决方案确实有效,但对我来说并不令人满意。

我真正想做的是在注册函数中写一个lambda。

优点一般是局部性:我能够捕获一些局部变量,并且不需要编写复杂的“actualCallback”成员例程。

我已经读过,在捕获变量时似乎不可能将Lambdas用作C风格的函数。

有什么方法可以通过一些技巧实现这一目标吗? (例如,使用lambdas的调用约定,...)

由于

3 个答案:

答案 0 :(得分:3)

捕获上下文变量的lambda无法转换为裸函数指针,因为这样就无法携带捕获的状态。您在示例中显示的是正确处理通过C回调调用C ++成员函数的问题的方法。

如果您发现更具吸引力的话,可以将c_callback_wrapper替换为少捕获lambda。

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, 
                 [](char *ptr, size_t size, size_t nmemb, void *userdata) {
                    // invoke the member function via userdata
                    auto p = static_cast<MyClass *>(userdata);
                    return p->actualCallback(ptr, size, nmemb, userdata);
                 });

请注意,您应该删除actualCallback()成员函数的最后一个参数,因为那只是this指针,不需要传递非静态成员函数明确。

答案 1 :(得分:3)

这就是我要做的事情(请注意我对C ++的了解非常充分,可以吹掉偶尔的脚):

#include <iostream>
#include <functional>

void external_c_function(void cb(void *), void *userdata)
{
    cb(userdata);
}

void c_callback_wrapper(void *userdata)
{
    auto &lambda = *static_cast< std::function<void(void)>* >(userdata);
    std::cout << "calling lambda" << std::endl;
    lambda();
}

int main(void)
{
    int foo = 42;
    std::function<void(void)> lambda = [&] { std::cout << foo << std::endl; };
    external_c_function(c_callback_wrapper, &lambda);
    return 0;
}

答案 2 :(得分:2)

template<class T>using type=T; // makes some declarations easier

template<class F>
struct callback_t;

template<class F, class Sig>
struct cast_helper_pvoid_last;
template<class F, class R, class... Args>
struct cast_helper_pvoid_last<F, R(Args...)> {
  type<R(*)(Args..., void*)> operator()() const {
    return [](Args... args, void* pvoid)->R {
      auto* callback = static_cast<callback_t<F>*>(pvoid);
      return callback->f( std::forward<Args>(args)... );
    };
  }
};

template<class F>
struct callback_t {
  F f;
  void* pvoid() { return this; }

  template<class Sig>
  auto pvoid_at_end()->decltype( cast_helper_pvoid_last<F, Sig>{}() ) {
    return cast_helper_pvoid_last<F,Sig>{}();
  }
};
template<class T>using decay_t=typename std::decay<T>::type;
template<class F>
callback_t<decay_t<F>> make_callback( F&& f ) { return {std::forward<F>(f)}; }

使用示例:

int x = 3;
auto callback = make_callback( [&]( int y ) { return x+y; } );
int (*func)(int, void*) = callback.pvoid_at_end<int(int)>();
std::cout << func( 1, callback.pvoid() ) << "\n";

should print 4. (live example)

callback_t的有效期必须超过其产生的pvoid

我可以自动推断函数指针的签名,而不是要求你传递<int(int)>(没有void*的签名),但这又使代码更容易。

如果您希望void*成为第一个,请添加此项:

template<class F, class Sig>
struct cast_helper_pvoid_first;
template<class F, class R, class... Args>
struct cast_helper_pvoid_first<class F, R(Args...)> {
  type<R(*)(void*, Args...)> operator()() const {
    return [](void* pvoid, Args... args)->R {
      auto* callback = static_cast<callback<F>*>(pvoid);
      return callback->f( std::forward<Args>(args)... );
    };
  }
};
// inside the callback_t<?> template:
  template<class Sig>
  auto pvoid_at_start()->decltype( cast_helper_pvoid_first<F, Sig>{}() ) {
    return cast_helper_pvoid_first<F,Sig>{}();
  }

在中间做一个void*变得更加棘手。

如果您有调用约定问题,那么我们需要使用辅助对象

pvoid_at_end返回cast_helper_pvoid_last而不是在其上调用()

然后,将operator重载添加到您需要支持的每个调用约定的cast-to-function-pointer。正文与operator()相同,因为lambda应支持其中任何一个。

或者,通过一些C ++ 14支持,您可以将operator()的返回类型更改为auto,并保持代码完整无缺,并依赖于直接cast-from-lambda来获取呼吁公约正确。