C++11中的C风格回调

C-style Callbacks in C++11

本文关键字:回调 风格 中的 C++11      更新时间:2023-10-16

在一个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的调用约定,…)

感谢

捕获上下文变量的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指针,非静态成员函数不需要显式传递该指针。

下面是我要做的(请注意,我对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;
}
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";

应打印4。(实例)

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重载以强制转换到您需要支持的每个调用约定的函数指针。主体与operator()相同,因为lambda应该支持其中任何一个。

或者,通过一些C++14支持,您可以将operator()的返回类型更改为auto,并在其他方面保持代码不变,并依靠lambda的直接转换来获得正确的调用约定。