在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的调用约定,...)
由于
答案 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来获取呼吁公约正确。