c++ Package std::function以忽略参数

zf2sa74q  于 2023-08-09  发布在  其他
关注(0)|答案(3)|浏览(188)

我正在使用图书馆:

  1. // External Library:
  2. typedef void (*callback_type)(void *arg);
  3. void external_register_callback(callback_type callback, void* args) {
  4. // ...
  5. }

字符串
它允许注册回调函数:

  1. void example_callback_function(void *arg) {
  2. // do something with *arg.
  3. }
  4. external_register_callback(example_callback_function);


我需要为库编写一个 Package 器层,它也允许注册回调函数,但不是带有void*参数的函数,而是应该接受不带参数的std::function。这就是我的 Package 应该看起来像:

  1. void my_register_callback(std::function<void()> callback) {
  2. external_register_callback(... callback ...); // how to "convert" callback to callback_type?
  3. }


它应该这样使用:

  1. void my_callback_function() {
  2. // do something
  3. }
  4. void my_register_callback(my_callback_function);

TL;DR:

如何将我的std::function<void()>转换为接受void*参数但忽略它的函数指针?

备注:

我用的是C++20
函数的“转换”应该(如果可能的话)在没有运行时开销的情况下完成。要明确的是:我不关心register_callback()的运行时,但是当外部库实际调用回调时,应该很快。

编辑:

external_register_callback()接受一个额外的void*参数,该参数传递给回调函数,我将其添加到代码中。

oyt4ldly

oyt4ldly1#

当你想把任何回调类型snd到c库时,有一个通用的模式。这并不是要删除参数,而是要将自己的回调放到上下文中。
为了简单起见,让我们想象一下,我们不需要担心生命周期。
该模式包括将回调作为具有工作调用语法的对象。它可以是一个函数指针,lambda或std::function。您设置void*指向它,然后发送一个函数指针,该指针接受上下文并将其转换回函数对象以调用它。
是这样的:

  1. template<std::invocable<> F>
  2. void my_register_callback(F& callback) {
  3. set_context(&callback); // sets the void*
  4. external_register_callback([](void* context) {
  5. // Retrieve the callback
  6. F* callback = static_cast<F*>(context);
  7. // Call it!
  8. (*callback)();
  9. });
  10. }

字符串
这允许设置任何对c库的回调,包括函数对象,函数指针,lambda表达式或其他多态 Package 器,如std::function
请记住,使用此模式,您可以完全跳过std::function可能发生的双重间接。
这种模式之所以有效,是因为没有捕获的lambda表达式可以转换为函数指针。
额外的好处是,您也不依赖于全局对象来使该调用与C库一起工作。
现在,对于生存期,我想说,你绝对可以在你的 Package 器层中管理函数对象的生存期,并根据你的需要保持它的生存期,最好直到你取消回调。

展开查看全部
7gcisfzg

7gcisfzg2#

您可以创建一个代理函数并存储它调用的std::function<void()>的列表。范例:

  1. #include <functional>
  2. #include <vector>
  3. #include <iostream>
  4. std::vector<std::function<void()>> proxy1_callbacks;
  5. extern "C" // probably
  6. void proxy1(void*) {
  7. for(auto&& func : proxy1_callbacks) func();
  8. }
  9. void my_register_callback(std::function<void()> callback) {
  10. proxy1_callbacks.emplace_back(std::move(callback));
  11. external_register_callback(proxy1);
  12. }

字符串
Demo
如果external_register_callback实际上也接受void*参数

  1. void external_register_callback(callback_type callback, void* userdata) {
  2. //...
  3. }


然后在调用时提供给回调函数(这很常见),您可以使用该void*指向您的std::function<void()>

  1. void my_register_callback(std::function<void()> callback) {
  2. // store the callbacks somewhere else if you need the capability
  3. // to unregister them later
  4. static std::list<std::function<void()>> lookup;
  5. // store the callback and get a pointer to it
  6. void* ptr = &lookup.emplace_back(std::move(callback));
  7. external_register_callback(
  8. [](void* userdata) {
  9. // cast back and call
  10. (*static_cast<std::function<void()>*>(userdata))();
  11. },
  12. ptr); // the pointer to your std::function<void()>
  13. }


Demo
除了使用单独的my_register_callback函数之外,还可以让类注册到C风格的回调框架中,以便在已注册的对象上调用特定的事件处理程序。
假设你有几个不同的C风格的注册函数用于你正在使用的API:

  1. void register_callback_x(callback_type callback, void* userdata) {}
  2. void register_callback_y(callback_type callback, void* userdata) {}


然后可以创建注册static成员函数的类的示例。在这些函数中,您将强制转换回class类型并调用(非static)目标成员函数。

  1. class callback_handler {
  2. public:
  3. callback_handler() {
  4. register_callback_x(on_x_proxy, this);
  5. register_callback_y(on_y_proxy, this);
  6. }
  7. ~callback_handler() {
  8. // unregister
  9. }
  10. void on_x() {
  11. // called on a specific object
  12. }
  13. void on_y() {
  14. // called on a specific object
  15. }
  16. private:
  17. // proxies that cast userdata to class type and calls the
  18. // non-static callback function
  19. static void on_x_proxy(void* userdata) {
  20. static_cast<callback_handler*>(userdata)->on_x();
  21. }
  22. static void on_y_proxy(void* userdata) {
  23. static_cast<callback_handler*>(userdata)->on_y();
  24. }
  25. };


Demo
如果你希望不同的类能够注册,你可以使用多态性。您可以通过使on_x/on_y成为virtual来使用运行时多态性,也可以在编译时使用CRTP来实现。下面是一个CRTP示例:

  1. template<class T>
  2. class handler_base {
  3. protected:
  4. handler_base() {
  5. register_callback_x(on_x_proxy, this);
  6. register_callback_y(on_y_proxy, this);
  7. }
  8. ~handler_base() {
  9. // unregister
  10. }
  11. private:
  12. static void on_x_proxy(void* userdata) {
  13. static_cast<T*>(userdata)->on_x();
  14. }
  15. static void on_y_proxy(void* userdata) {
  16. static_cast<T*>(userdata)->on_y();
  17. }
  18. };
  1. class callback_handler_1 : handler_base<callback_handler> {
  2. public:
  3. void on_x() {}
  4. void on_y() {}
  5. };
  6. class callback_handler_2 : handler_base<callback_handler> {
  7. public:
  8. void on_x() {}
  9. void on_y() {}
  10. };

的字符串
Demo

展开查看全部
f45qwnt8

f45qwnt83#

如果你可以控制函数接收到的void*,你应该让它指向你想要调用的std::function<void()>示例,然后在你的自定义回调中调用它:

  1. void my_callback(void* p) {
  2. const auto f = static_cast<std::function<void()>*>(p);
  3. if (f) {
  4. (*f)();
  5. }
  6. }

字符串
否则,您需要一个全局示例,您的回调可以从其中调度:

  1. thread_local std::function<void()> f;
  2. void my_callback(void* p) {
  3. if (f) {
  4. f();
  5. }
  6. }

展开查看全部

相关问题