【问题标题】:How to pass a c++ class function as callback如何将 C++ 类函数作为回调传递
【发布时间】:2026-01-09 12:15:01
【问题描述】:

我在理解如何将 this 传递给传递给库的回调函数时遇到了一些麻烦。该函数需要有一个特定的签名。

有问题的库是 OCILIB (https://vrogier.github.io/ocilib/doc/html/classocilib_1_1_subscription.html) 并尝试将类函数作为 Register() 的第四个参数传递。

我可以按如下方式顺利通过

&database::callback // a static function of database

[](ocilib::Event &event) // as lambda function
{
}

但它无权访问实例变量。我试着像这样使用它

[&](ocilib::Event &event) // as lambda function
{
}

但签名不匹配,我收到以下错误

database.cpp: In member function ‘bool dcn::database::watch(std::__cxx11::string)’:
database.cpp:104:44: error: no matching function for call to ‘ocilib::Subscription::Register(ocilib::Connection&, std::__cxx11::string&, ocilib::Subscription::ChangeTypesValues, dcn::database::watch(std::__cxx11::string)::<lambda(ocilib::Event&)>, unsigned int, unsigned int)’
    }, (unsigned int) 7778, (unsigned int) 0);
                                            ^
In file included from /usr/include/ocilib.hpp:9194:0,
                 from /home/ai/dcn/include/main.h:17,
                 from database.cpp:1:
/usr/include/ocilib_impl.hpp:6650:13: note: candidate: void ocilib::Subscription::Register(const ocilib::Connection&, const ostring&, ocilib::Subscription::ChangeTypes, ocilib::Subscription::NotifyHandlerProc, unsigned int, unsigned int)
 inline void Subscription::Register(const Connection &connection, const ostring& name, ChangeTypes changeTypes, NotifyHandlerProc handler, unsigned int port, unsigned int timeout)
             ^~~~~~~~~~~~
/usr/include/ocilib_impl.hpp:6650:13: note:   no known conversion for argument 4 from ‘dcn::database::watch(std::__cxx11::string)::<lambda(ocilib::Event&)>’ to ‘ocilib::Subscription::NotifyHandlerProc {aka void (*)(ocilib::Event&)}’
make[1]: *** [database.o] Error 1

函数定义为

static void callback(ocilib::Event &);

需要您的帮助来解决这个问题。提前致谢。

【问题讨论】:

  • 是的 - 经典问题。 OCILIB 仅允许您为回调注册 void (*)(ocilib::Event&amp;),并且只能与静态成员或独立函数匹配。您需要编写这样一个函数,该函数在内部查阅一些记录以计算出调用成员函数的事件。例如,它可能会在回调期间查看 ocilib::Event 参数内部,然后使用一些 id 查找关联容器(mapunordered_map)以找到指向对象的指针以调用非静态成员函数开。
  • (如果 OCILIB 的 Subscription::Register 用现代 C++ 重写,它应该更改为 std::function 参数而不是非成员函数指针参数,这将允许您通过 lambdas捕获。)
  • 如果 API 没有为您提供通过void* 的选项,那么如果不求助于不太理想的解决方案,您将无能为力。你能提供一些关于这个电话的背景信息吗?进行调用的代码可以在同一个程序执行中多次运行吗?
  • @chris 是的,它会被调用很多次。回调是钩子的一部分,它将等待数据库中的更改并复制到不同的数据库。
  • 我不是指回调本身,而是注册回调的代码。有不同级别的hacky解决方案,一种是生成一个函数指针,如果再次调用注册码,该函数指针将被覆盖。其他级别是不同程度的更多工作,涵盖更少或更弱的假设。

标签: c++


【解决方案1】:

这是一个可怕的 API。任何拥有回调函数但又不要求 void* 的人正在编写我们知道在 70 年代是个坏主意的代码。

你别无选择,只能使用全局状态来分派回你的班级。

template<std::size_t N, class R, class...Args>
struct crappy_api_fix{
  static std::array< std::function<R(Args&&...)>, N >& table(){
    static std::array< std::function<R(Args&&...)>, N > arr;
    return arr;
  }
  template<std::size_t I>
  static R call( Args...args ){ return table()[I]( std::forward<Args>(args)... ); }
  using sig=R(Args...);
  template<std::size_t I=N-1>
  static sig* make(std::function<R(Args&&...)> f){
    if(!table()[I]){
      table()[I]=f;
      return &call<I>;
    }
    if(I==0) return nullptr;
    return make< (I-1)%N >(f);
  }
  template<std::size_t I=N-1>
  static void recycle( sig* f ){
    if (f==call<I>){
      table()[I]={};
      return;
    }
    if (I==0) return;
    recycle< (I-1)%N >( f);
  }
};

类似的东西。它维护了一个N std 函数的全局表,并返回一个知道要调用哪个 std 函数的函数指针。

// up to 50 callbacks alive at once:
using cb_helper = crappy_api_fix<50, void, ocilib::Event &>;

// "stateless" function pointer wrapping a lambda:
void(*f)(ocilib::Event&) = cb_helper::make([&](ocilib::Event &event) {});

// blah is the API class with the Register method.  We pass the f from above:
blah->Register(arg1, arg2, f, arg4);

// This might be far away fron the above code:
// we should keep copy of the f; when we have unregistered, we shoukd recyle it:
cb_helper::recycle(f); // when `f` is no longer needed

抱歉错别字,请在我的手机上输入。

【讨论】:

    最近更新 更多