【问题标题】:C-style Callbacks in C++11C++11 中的 C 风格回调
【发布时间】:2014-10-20 05:24:06
【问题描述】:

在一个 C++11 项目中,我使用了一个 C 风格的第三方库(在我的例子中是 curl),它需要 C 风格的回调。

为了实现这一点,我使用了“指向成员”运算符:

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”成员例程。

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

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

谢谢

【问题讨论】:

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


    【解决方案1】:

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

    如果您发现它更有吸引力,您可以用捕获较少的 lambda 替换 c_callback_wrapper

    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 指针,非静态成员函数不需要显式传递。

    【讨论】:

    • 我已经尝试过了,但是我的应用程序最终会出现段错误 - 即使我尝试使用非捕获 lambda... 将第三方 c 样式库与直接 C++11 相结合代码总是扼杀代码之美...我讨厌它... :-(
    • @rralf does work 上面的解决方案,所以也许你还有其他事情要做。如果您使用的是 MSVC,请确保您 cast the lambda 匹配 C 库使用的调用约定。
    • 哦,谢谢,使用演员表就可以了! (似乎 gcc 也需要强制转换来调用约定)
    • 这应该只在 lambda 不捕获任何变量时才有效!
    • 是的,我知道。如果在使用捕获时有一些技巧可以让它工作,那就太好了......
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:
      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

      我可以自动推断函数指针的签名,而不是要求您传递&lt;int(int)&gt;(没有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,否则代码保持不变,并依靠直接 cast-from-lambda 来获得正确的调用约定。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-01-12
        • 1970-01-01
        • 2012-11-06
        • 2010-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多