【问题标题】:How to use c++14 variadic templates with sdbus callbacks如何在 sdbus 回调中使用 c++14 可变参数模板
【发布时间】:2017-12-15 19:14:38
【问题描述】:

sd-bus 在定义 d-bus 方法时需要一个回调函数。当我在做 C++14 时,我希望对类对象 on_msg_method_here() 函数进行这些调用。我想要实现的是这样的(在伪 C++ 中):

int callback_dbus_method_foo( message* msg, void* userdata, ... )
{
     MyClass* cls = (MyClass*)userdata;
     Type0 var0;
     if ( message_process( msg, signature[0], &var0 ) != 0 )
        //.. error here
     Type1 var1;
     if ( message_process( msg, signature[1], &var1 ) != 0 )
        //.. error here
     //... and these continue from 0 to N
     TypeN varN;
     if ( message_process( msg, signature[N], &varN ) != 0 )
        //.. error here
     int dbus_ret = cls->on_msg_method_foo( var1, var2, ..., varN )          
     handle_dbus_ret( msg, dbus_ret // ... );
     return 0;
}

int MyClass::register_callbacks( ... )
{

  // Well really we have something really different, this is to demonstrate

  // pass 'this' as userdata* to callback function
  dbus_register_callback( "method_foo", 
         &callback_dbus_method_foo, this )
}

现在我知道我可以使用 C 宏来做到这一点,但是如何正确地 使用 C++14 可变宏来做到这一点?

据我了解,调用某个类对象某个方法的麻烦可以用 std::bind 处理(并通过 userdata 指针传递),变量声明和 message_process 可以用可变参数模板剥离来完成,但是如何让那些声明的变量(伪代码示例中的 var0、var1、..)正确扩展为调用?简而言之,如何做到这一点:

MyClass::register_callbacks()
{
   Mystic fun_to_call = std::bind( &MyClass::on_dbus_method_foo, this ); 
   dbus_register_callback( "method_foo", 
            super_magic_template<int,double,bool>, &fun_to_call );
}

【问题讨论】:

  • 注册本地函数的地址不会让你特别远:调用fun_to_call时,你将访问一个被破坏的对象。您需要保留已注册的对象。我对 sd-bus 了解不多,无法评论如何给它一个更好的 C++ API。

标签: c++ c++11 c++14 variadic-templates dbus


【解决方案1】:

为了获得一个优雅而通用的解决方案,我会做几件事。

我们需要一种方法来收集变量(var0、var1、...、varN)并将它们传递给函数。为此,我将首先有一个包装器来查询这些变量,因为它的索引是 i。我不确定您的示例中的 signature 是什么,但我相信您可以解决这个问题。

template <class T>
T get_var(message* msg, unsigned i) {
  T var;
  if ( message_process( msg, signature[i], &var ) != 0)
    throw std::runtime_error("Oups"); // In this context, it's easier to deal with errors with exception.
  return var;
}

然后我们可以通过解包可变参数模板参数以及用于索引的相关 index_sequence 来收集所有变量。类似的东西

template <class... Vars, class F>
void callback_wrapper(F& fcn, message* msg) {
  callback_wrapper_impl(fcn, msg, std::make_index_sequence<sizeof...(Vars)>());
}
template <class... Vars, class F, size_t... i>
void callback_wrapper_impl(F& fcn, message* msg, std::index_sequence<i...>) {
  fcn(get_var<Vars>(msg, i)...);
}

使用 std::bind 会产生另一个困难,它返回类似函数的对象 fun_to_call。我们不能将它作为函数指针传递给dbus_register_callback,它不携带任何数据,我们也不能将指针作为用户数据传递给它,因为fun_to_call是一个局部变量,因此它的生命周期太短了。

我不会只依赖super_magic_template 回调,而是对dbus_register_callback 进行包装,提供更简单的接口,我们称之为modern_dbus_register_callback。我看到的最直接的解决方案是以内存分配和额外的间接级别为代价使用动态存储持续时间 - 这类似于 std::function 中使用的类型擦除。请注意,如果sizeof(fun_to_call) &lt; sizeof(void*),您可以通过按值传递fun_to_call 作为用户数据来优化它 - 这是小值优化。我相信使用没有捕获的 lambdas 会很有用,因为它们可以转换为函数指针并避免大量模板样板。可能需要一些额外的工作来处理错误,同时避免内存泄漏。

template <class... Vars, class F>
void modern_dbus_register_callback(const char* name, F& fcn) {
  std::unique_ptr<F> fcn_ptr = std::make_unique<F>(fcn);
  dbus_register_callback(name, [](message* msg, void* userdata){
    std::unique_ptr<F> fcn_ptr(static_cast<F*>(userdata));
    callback_wrapper<Vars...>(*fcn_ptr, msg);
  }, fcn_ptr.release());
}

这可以用作

modern_dbus_register_callback<int,double,bool>("method_foo", fun_to_call);

【讨论】:

  • 超级! const char* 签名实际上是根据 dbus-signature 定义列出参数的类型。我现在才意识到,使用参数类型为 oveloading 的 'get_dbus_signature 可以很好地处理这一点。 std::bind 的事情可能也可以通过将它们设为类变量来处理 - 或者至少我不明白为什么不这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-18
  • 2012-11-27
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多