使用 C++20 概念来避免 std::function

Using C++20 concepts to avoid std::function

以前,当我想要一个回调作为函数参数时,我通常决定使用std::function。在我绝对从不使用捕获的极少数情况下,我使用 typedef 作为函数声明。

所以,通常我带有回调参数的声明看起来像这样:

struct Socket
{
  void on_receive(std::function<void(uint8_t*, unsigned long)> cb);
}

但是,据我所知,std::function 实际上在运行时做了一些工作,因为必须通过捕获到 std::function 模板和 move/copy 来解析 lambda它捕获 (?)。

阅读新的 C++ 20 功能后,我认为我可以利用概念来避免使用 std::function 并为任何可行的函子使用约束参数。

这就是我的问题出现的地方:因为我想在将来的某个时候使用回调仿函数对象,所以我必须存储它们。由于我没有明确的回调类型,我最初的想法是复制(最终在某个时候移动)仿函数堆并使用 std::vector<void*> 来记录我离开它们的位置。

template<typename Functor>
concept ReceiveCallback = std::is_invocable_v<Functor, uint8_t*, unsigned long>
                       && std::is_same_v<typename std::invoke_result<Functor, uint8_t*, unsigned long>::type, void>
                       && std::is_copy_constructible_v<Functor>;
struct Socket
{
  std::vector<void*> callbacks;

  template<ReceiveCallback TCallback>
  void on_receive(TCallback const& callback)
  {
    callbacks.push_back(new TCallback(callback));
  }
}

int main(int argc, char** argv)
{
  Socket* sock;
  // [...] inialize socket somehow

  sock->on_receive([](uint8_t* data, unsigned long length)
                   {
                     // NOP for now
                   });

  // [...]
}

虽然这工作得很好,但在实现应该调用仿函数的方法时,我注意到我刚刚推迟了 unknown/missing 类型的问题。就我的理解而言,将 void* 转换为函数指针或一些类似的 hack 应该会产生 UB - 编译器怎么知道,我实际上是在尝试调用 class 的 operator()完全未知?

我考虑过将(复制的)仿函数连同指向它的 operator() 定义的函数指针一起存储,但是我不知道如何将仿函数作为 this 注入函数内部,并且没有它,我怀疑捕获是否有效。

我采用的另一种方法是声明一个纯虚拟接口,该接口声明所需的 operator() 函数。不幸的是,我的编译器禁止我将仿函数转换为接口,而且我认为也没有合法的方法让 lambda 从中派生。

那么,有没有办法解决这个问题,或者我可能只是误用了模板 requirements/concepts 功能?

您的初始版本使用 std::function 正好 因为 它删除了类型。如果您想要类型擦除(您显然是这样做的,因为您希望用户能够在您的代码明确知道该类型是什么的情况下使用任何类型),那么您需要某种形式的类型擦除。类型擦除不是免费的。

约束是针对模板的。您 不需要 模板函数;你想要一个处理 type-erased 可调用函数的函数。

对于必须比提供者的调用堆栈更有效的回调,std::function 的开销正是您所需要的。也就是说,“开销”并非毫无意义;它允许您在回调处理器中存储任意未知类型的对象。