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

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

我正在使用图书馆:

// External Library:
typedef void (*callback_type)(void *arg);

void external_register_callback(callback_type callback, void* args) {
    // ...
}

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

void example_callback_function(void *arg) {
    // do something with *arg.
}

external_register_callback(example_callback_function);


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

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


它应该这样使用:

void my_callback_function() {
    // do something
}

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*指向它,然后发送一个函数指针,该指针接受上下文并将其转换回函数对象以调用它。
是这样的:

template<std::invocable<> F>
void my_register_callback(F& callback) {
    set_context(&callback); // sets the void*
    external_register_callback([](void* context) {
        // Retrieve the callback
        F* callback = static_cast<F*>(context);

        // Call it!
        (*callback)();
    });
}

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

7gcisfzg

7gcisfzg2#

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

#include <functional>
#include <vector>
#include <iostream>

std::vector<std::function<void()>> proxy1_callbacks;
extern "C" // probably
void proxy1(void*) {
    for(auto&& func : proxy1_callbacks) func();
}

void my_register_callback(std::function<void()> callback) {
    proxy1_callbacks.emplace_back(std::move(callback));
    external_register_callback(proxy1);
}

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

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


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

void my_register_callback(std::function<void()> callback) {
    // store the callbacks somewhere else if you need the capability
    // to unregister them later
    static std::list<std::function<void()>> lookup;

    // store the callback and get a pointer to it
    void* ptr = &lookup.emplace_back(std::move(callback));

    external_register_callback(
        [](void* userdata) {
            // cast back and call
            (*static_cast<std::function<void()>*>(userdata))();
        },
        ptr); // the pointer to your std::function<void()>
}


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

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


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

class callback_handler {
public:
    callback_handler() {
        register_callback_x(on_x_proxy, this);
        register_callback_y(on_y_proxy, this);
    }
    ~callback_handler() {
        // unregister
    }
    void on_x() {
        // called on a specific object
    }
    void on_y() {
        // called on a specific object
    }

private:
    // proxies that cast userdata to class type and calls the 
    // non-static callback function
    static void on_x_proxy(void* userdata) {
        static_cast<callback_handler*>(userdata)->on_x();
    }
    static void on_y_proxy(void* userdata) {
        static_cast<callback_handler*>(userdata)->on_y();
    }
};


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

template<class T>
class handler_base {
protected:
    handler_base() {
        register_callback_x(on_x_proxy, this);
        register_callback_y(on_y_proxy, this);
    }
    ~handler_base() {
        // unregister
    }
    
private:
    static void on_x_proxy(void* userdata) {
        static_cast<T*>(userdata)->on_x();
    }
    static void on_y_proxy(void* userdata) {
        static_cast<T*>(userdata)->on_y();
    }
};
class callback_handler_1 : handler_base<callback_handler> {
public:
    void on_x() {}
    void on_y() {}
};

class callback_handler_2 : handler_base<callback_handler> {
public:
    void on_x() {}
    void on_y() {}
};

的字符串
Demo

f45qwnt8

f45qwnt83#

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

void my_callback(void* p) {
   const auto f = static_cast<std::function<void()>*>(p);
   if (f) {
      (*f)();
   }
}

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

thread_local std::function<void()> f;

void my_callback(void* p) {
   if (f) {
      f();
   }
}

相关问题