【问题标题】:How to store various types callback functions in one container如何在一个容器中存储各种类型的回调函数
【发布时间】:2018-12-08 19:11:35
【问题描述】:

我需要将 opencl 中的回调函数存储到以后执行,例如:

void pfn_notify(const char *errinfo, const void *private_info, size_t cb, void *user_data){
    fprintf(stderr, "OpenCL Error (via pfn_notify): %s\n", errinfo);
}

这些回调函数各不相同,所以我想统一存储方法并将指向它们的指针存储在某种容器中(vectormap 等)。此外,有些参数需要在存储后传递,而不是在推入容器时进行绑定。

  1. 通用伪代码方案:

    ...some code in main...
    Client.storecallback(function_to_store(some_arguments));
    ...rest code in main...
    
    class Client{
      void storecallback(void*(*)(some_arguments) function){
        callback_thread.store(function);
        callback_thread.start();
      }
    
      CallbackThread callback_thread;
    };
    
    class CallbackThread() {
      void start(){
       /* make connection via tcp/ip */
       receive();
      }
      void store(void*(*) function){
        callback.store(key, function);
      }
    
      void receive() {
        Buffer* input = new Buffer(tcp_socket);
        int a = input->pull();
        char b = input->pull();
        callback.raise(key, a, b);
      }
    
      Callback callback;
    };
    
    class Callback {
      std::map<uint64_t key, void*(*) function> callback_map;
      void store(void*(*) function){
        callback_map[key] = function;
      }
      template<typename... Args>
      void raise(uint64_t key, Args... rest_of_arguments){
        callback_map[key](rest_of_arguments);
      }
    };
    

我知道我需要额外的课程来统一,某种Functor 课程。

  1. 使用std::functionstd::bind 我可以将存储的变量类型统一为std::function&lt;void()&gt; 但是,我无法更改/绑定新参数到回调函数。这个解决方案需要通用struct,它将存储指向参数变量的指针并在调用存储函数之前用数据替换/填充它们,我不知道如何为每个回调函数创建这样的通用结构而不是结构模板,这不是真的对我来说很好的解决方案。

  2. IndiciesStackoverflow 的帮助下,我能够创建解决方案,在该解决方案中我可以将创建过程与占位符统一起来,这允许我在调用时添加一些参数,而不仅仅是存储:

     namespace project_name {
       namespace detail {
        template<int I> struct placeholder {};
       }
     }
     namespace std {
       template<int I>
       struct is_placeholder< project_name::detail::placeholder<I> > :
       std::integral_constant<int, I>{};
     }
     namespace project_name {
       namespace detail {
         template <size_t... Is>
         struct indices {};
    
         template <size_t N, std::size_t... Is>
         struct build_indices : build_indices<N - 1, N - 1, Is...> {};
    
         template <size_t... Is>
         struct build_indices<0, Is...> : indices<Is...> {};
    
         template<std::size_t... Is, class F, class... Args>
         inline auto easy_bind(indices<Is...>, F const& f, Args&&... args) ->
         decltype(std::bind(f, std::forward<Args>(args)..., placeholder<Is + 1> {}...)){
        return std::bind(
          f, std::forward<Args>(args)..., placeholder<Is + 1> {} ...);
       }
     }
    
     class Functor {
       public:
    
       template<class R, class... FArgs, class... Args>
       Functor(std::function<R(FArgs...)> f, Args&&... args) {}
    
       template<class R, class... FArgs, class... Args>
       static inline auto easy_bind(std::function<R(FArgs...)> f, Args&&... args) -> decltype(detail::easy_bind(
      detail::build_indices<sizeof...(FArgs) - sizeof...(Args)> {},
      f, std::forward<Args>(args)...)) {
        return detail::easy_bind(
        detail::build_indices<sizeof...(FArgs) - sizeof...(Args)> {}, f, std::forward<Args>(args)...);
     }
    }
    

不幸的是,现在我不知道如何统一存储它,因为使用 std::placeholder 时返回的类型不同。

这是我的问题:

  1. 这两种方法中哪一种更好,以及如何解决这种方法的问题。
  2. 也许我应该考虑其他方法,但由于缺乏知识而没有想到。

编辑: 你的 cmets 链接到关于问题的线程甚至不接近我的问题是绝对没有用的。在链接我已经实现并插入到我的问题中的内容之前,请阅读问题是关于什么的。无论如何都解决了,我会尽快为后代添加解决方案。

【问题讨论】:

  • 听起来你想要独特的std::function 签名和捕获 lambda 函数。
  • 问两个 C++ 开发者哪两种方法更好,你会得到三个答案和四个替代方案。
  • 对于每个投反对票的人,请让我知道您认为这个问题有什么不对,因为我不知道。
  • 另一种选择可能是继承具有virtual void operator()() 函数的抽象基类的类。该函数调用运算符可以在子类中实现,以使用所需的任何参数调用它需要的任何其他函数。并且参数可以是通过构造函数初始化的子类中的成员。

标签: c++ c++11 opencl


【解决方案1】:

好的,有点晚了,但我想确定一下,圣诞节假期也来了。这是 Functors 模板类的完整示例,稍后可以将其存储在 map 中,并使用各种参数轻松调用。这就是我所说的,我敢肯定除了Some programmer dude(可能是因为我的解释不好),没有人提供任何有用的建议。我们在这里使用了一些花哨的技巧(主要由堆栈溢出和文档提供)。

这是包含我们所有回调的类的草图,我们通过将它们重构为 std::shared_ptr&lt;void&gt; 来存储统一的回调函数。这是必要的,因为它执行类型擦除。这允许我们将所有这些都存储在一个映射中,而不管参数类型如何。然而,指针析构函数将使用正确的deleter 调用,该deleter 存储在内部。更多信息:Stackoverflow std::shared_ptr

class Event {
 public:
  template<typename... Args>
  size_t addFunction(uint64_t callback_id, void(*callback)(Args...), Args... args) {
    std::shared_ptr<Functor<Args...>> ptr(new Functor<Args...>(this->p_input, callback, args...));

    std::map<uint64_t, std::shared_ptr<void>>::const_iterator it = p_functionMap.find(callback_id);

    if (it == p_functionMap.end()) {
      p_functionMap[callback_id] = ptr;

      return ++p_functionIndex;
    } else {
      std::cout << "WE HAVE THIS CALLBACK ALREADY!: " << callback_id << std::endl;
    }
    return p_functionIndex;
  }

  size_t removeFunction(uint64_t callback_id) {
    p_functionIndex -= p_functionMap.erase(callback_id);
    return p_functionIndex;
  }

 private:
  explicit Event(Buffer* input) :
  p_input(input) {
    p_functionIndex = 0;
  }

 private:
  std::atomic<size_t> p_functionIndex;
  std::map<uint64_t, std::shared_ptr<void>> p_functionMap;
  Buffer* p_input;
};

这是模板类的草图,其中包含有关我们的函子的所有必要信息。我们使用参数包来避免定义参数类型。参数存储在std::tuple 中,这使我们可以稍后在调用回调函数时使用它们,也可以将其中一些与新的交换(例如 OpenCL 回调)。对参数的每个基本操作都可以在析构函数中执行。在析构函数内部引发回调之后,这就是所有人!更多信息:Stackoverflow unpack parameter packs to call function pointer

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
swapArguments(std::tuple<Tp...>& t, cccl::Buffer* input) { }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if < I < sizeof...(Tp), void>::type
swapArguments(std::tuple<Tp...>& t, cccl::Buffer* input) {
  using ARG = std::remove_reference<decltype(std::get<I>(t))>::type;

  /*
    HERE NEW TUPLE AND SWAP ARGUMENTS
    OR ANYTHING ELSE(FOR EXAMPLE BUFFER DATA
    LIKE BELOW)
  */

  std::get<I>(t) = p_input->pull<ARG>();
  swapArguments<I + 1, Tp...>(t, input);
}

template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N - 1, N - 1, S...> {};
template<int ...S> struct gens<0, S...> { typedef seq<S...> type; };

template<typename... Args>
class Functor {
  std::tuple<Args...> arguments;
  void(*callback)(Args...);
  Buffer *p_input;

public:
 void dispatchCallback()
 {
    return callbackFunction(typename gens<sizeof...(Args)>::type());
 }

 template<int ...S>
 void callbackFunction(seq<S...>)
 {
   return this->callback(std::get<S>(this->arguments) ...);
 }

 Functor(Buffer *input, void(*callback)(Args...), Args... args) {
   this->p_input = input;
   this->arguments = std::make_tuple(args...);
   this->callback = callback;

 }

 ~Functor() {
    swapArguments(this->arguments, this->p_input);
    this->dispatchCallback();
 }
};

我希望我已经正确解释了所有内容。如果需要更详细的描述,请告诉我,我会尝试扩展我的答案。新年快乐!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-15
    • 1970-01-01
    • 2015-03-26
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    相关资源
    最近更新 更多