代码之家  ›  专栏  ›  技术社区  ›  Colliot

是否可以将C风格的api回调/处理程序适配到C++,以便与类相关的动态创建?

  •  0
  • Colliot  · 技术社区  · 9 年前

    假设我们有类似这样的C风格API:

     void register_callback(target, callback_function);
    

    哪里 target 是某种对象,例如服务器,并且 call_back 必须是函数指针。

    现在我们想适应(更准确地说, 添加包装层 . 我们无法访问底层C代码 ,因为它是第三方库)。我们想要OOP,所以我们将目标做成一个类:

    class Target {
      Target(): target() { register_callback(target, callback_function_member) }
      private:
        target_t target;
        void callback_function_member (param_t parameter) { /* a set of things to do with the members */ }
        // other members
    }
    

    我们可能希望使回调成为如上所述的成员函数,因为它(在某些情况下)与实例紧密相连。但这行不通。无法将成员函数调整为函数指针(请参见,例如。 Pointers to Member Functions ).

    我在互联网上找到的所有解决方案(包括上一个)都只处理只有一个实例负责这项工作的情况。在那里我们可以专门编写一个包装函数(请参见 1 )或包装类的静态函数。

    问题是,如果我们想要多个实例,而且还需要动态创建它们呢?您可以为每个实例编写一个包装函数或类,如

    Target *pTarget;
    void wrapper(param_t parameter) {
      pTarget->callback_function_member(parameter);
    }
    int main() {
      Target myTarget();
      pTarget = &myTarget();
      myTarget.register(wrapper);
      // some work
      return 0;
    }
    

    但您不能为动态创建的每个实例手动编写函数。而且似乎不可能将任何类型的信息传递到全局函数中(例如,将实例存储在全局容器中,但如何在调用时传递索引?)。

    std::bind 接近解决方案(我不知道其存在),但关键问题是 标准::绑定 不能强制转换为函数指针。使用 target<type_of_function_pointer>() 属于 std::function 给我一个 nil ,因为类型不匹配。当我假装它是一个函数指针并直接传递它时,就会发生segfault。

    这到底是可能的吗?还是我的目标被误导了?处理这个问题的标准方法是什么?

    1 回复  |  直到 9 年前
        1
  •  2
  •   AnT stands with Russia    9 年前

    如果你真的需要一个C风格的回调,即通过一个正则函数指针调用目标回调,那么处理这个问题的标准方法是将实际回调实现为一个“正则”(非成员或静态成员)函数,并将对象指针作为一个额外的参数传递给它

    class SomeClass {
      ...
      void member_callback(int param1, double param2) { ... }
      static void static_callback_wrapper(int param1, double param2, void *user_param) {
        return static_cast<SomeClass *>(user_param)->member_callback(param1, params2);
      }
    };
    

    现在,如果你有一个对象

    SomeClass some_object;
    

    如果你想对这个对象使用这个回调,在某种外部算法中,你可以将一个指针传递给 SomeClass::static_callback_wrapper 作为回调,以及指向 some_object 作为“ void * 不透明的用户指定数据”。当然,该算法必须在实现时考虑到用户指定的数据-它必须转发 空隙,空隙* 从外部传递回回调的指针。

    例如,这是惯例 pthread_create 在pthreads库中如下( http://man7.org/linux/man-pages/man3/pthread_create.3.html ). 传递给该函数的回调是 void *(*start_routine) (void *) ,“用户指定的数据”通过最后一个 void *arg 参数线程函数将被调用为 start_routine(arg) 从而将用户指定的指针转发回回调。

    相同的约定用于非标准 qsort_r 功能( http://linux.die.net/man/3/qsort_r ). 基本上,将这种“不透明的用户指定数据”转发机制包含到使用C风格回调的每个实现中是一种很好的编程实践。

    所有这些都适用于您真正需要符合C风格回调接口的情况。在纯C++中,您还有其他更灵活的机会。您可以用 std::function 对象(作为一种可能性),并简单地绑定隐式 this 将成员函数的参数设置为要用于回调的对象,从而将它们转换为“普通”函数。

    void some_algorithm(std::function<void ()> callback) {
       ...
       callback();
       ...
       callback();
       ...
    }
    
    class SomeClass {
      void foo() {}
    };
    
    int main() {
      SomeClass object;
      some_algorithm(std::bind(&SomeClass::foo, &object));
    }