【问题标题】:Is it possible to adapt C style api callback/handler to C++ for class related dynamic creation?是否可以将 C 风格的 api 回调/处理程序改编为 C++ 以进行与类相关的动态创建?
【发布时间】:2016-05-31 01:24:55
【问题描述】:

假设我们有一些像这样的 C 风格 API:

 void register_callback(target, callback_function);

,其中target 是某种对象,例如一个服务器,call_back 必须是一个函数指针。

现在我们要适应(更准确地说,添加一个包装层我们无法访问底层 C 代码,因为它是一个第三方库)这个 API 到 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 接近解决方案(我不知道它的存在),但关键问题是std::bind 的返回值不能转换为函数指针。使用target<type_of_function_pointer>()std::function 得到nil,因为类型不匹配。当我假装它是一个函数指针并直接传递它时,就会发生段错误。

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

【问题讨论】:

  • 通常这将使用void* 来完成,回调函数会将其作为参数。您的注册函数还需要获取此指针并存储它,以便在调用它时将其传递给回调函数。
  • 这可能很简单:将回调设为接收void * instance 的静态成员。在其中,将其转换回this 并用它调用私人成员。要注册,只需注册this 并回调。
  • @Benjamin Lindley,@user3528438,实际上库是 libuv,API 是 int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb)。不知道是不是我用错了。
  • 这是一个改编自另一个问题的已删除答案的演示。也许它有帮助。 ideone.com/6sCsdV
  • @user3528438,在搜索void*和“libuv”后,我发现在文档中存在需要传入this的位置,即uv_stream_tdata

标签: c++ c callback function-pointers member-functions


【解决方案1】:

如果你真的需要 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 * opaque user-specified data”传递给那个算法。当然,算法在实现时必须考虑到用户指定的数据——它必须将从外部传递的 void * 指针转发回回调。

例如,这是 pthreads 库中遵循的约定 pthread_create (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));
}

【讨论】:

  • 同样在C++中,很多时候我们根本不需要使用动态绑定,只需将函数类型作为模板参数即可,这通常比函数指针更高效或std::function(通常可以内联)
  • 可能我的话有误导性?我使用了“适应”这个词,但更准确地说,我所拥有的可能是 wrap。我无法更改 C api(例如第三方库或 OS api。)。
  • @Aszune 的心:首先,我的回答的第一部分特别适用于在 C++ 中使用 C 风格的回调 API。静态成员函数是“常规”函数,可以作为回调传递给 C API。然而,其次,正如我上面所说,C API 必须遵循带有额外用户指定参数的约定。该参数对于将特定对象指针传递给回调是绝对必需的。如果您的 C API 遵循该约定,那么正如我上面所描述的,一切都很容易。如果它不遵循该约定,那么抱歉,该问题没有可靠的解决方案。
  • @AnT,静态函数是我在 stackoverflow 和其他地方读到的解决方案之一。是的,API 没有公开像 user_param 这样的参数。
  • @Aszune 的心:如果 API 没有公开额外的参数来将用户数据转发给回调,那么不幸的是根本没有可行的解决方案。为了将额外的数据传递给回调,您将被限制为全局变量(您在问题中已经提到过)以及它们的所有限制:没有线程安全,没有可重入性,无法在运行中“制造”回调到对象的绑定 -时间量(使用动态创建的对象)。更准确地说,一些编译器提供了有助于解决这种情况的非标准特性(也称为闭包),但这是高度特定于编译器的。
猜你喜欢
  • 2021-10-01
  • 2021-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-16
  • 2019-08-20
相关资源
最近更新 更多