具有变量捕获的多回调结构的惯用法

时间:2018-06-07 10:18:45

标签: c++ callback inversion-of-control

我有一个函数,它接受一个对象并在其上调用各种回调函数作为其输出。例如:

template<typename T>
void iterateInParallel(vector<int> const& a, vector<int> const& b, T && visitor)
{
    // sometimes invokes visitor.fromA(i)
    // sometimes invokes visitor.fromB(i)
    // sometimes invokes visitor.fromBoth(i, j)
}

(不要完全了解该功能的作用,这是一个例子,在这里不相关。)

现在,在编写代码以使用带回调对象的函数时,我经常会使用带有默认by-ref捕获的本地lambda来保持代码的紧密性和可读性。

int sum = 0;
int numElems = 0;
someFunctionTakingACallback(args, [&](int x) {sum += x; numElems++; });

这比旧的仿函数方法更好很多,我在其中定义了一个类,在其构造函数中将refs引入sumnumElems。与仿函数方法一样,它通常没有运行时成本,因为在实例化函数时已知回调代码。

但是lambda只是一个函数,因此它不适用于上面的iterateInParallel方法;我回来复制refs了。

所以我正在寻找的是一种从lambdas的default-capture获得大致方便性,性能和惯用性的方法,同时仍能调用多种类型的回调。

我考虑的选项:

  • 传递多个回调函数。这不是太糟糕,通常是我所追求的。但是很容易让参数顺序混淆,并且它可以真正膨胀参数列表。它也不那么自我记录,因为回调的名称在用户代码中没有。
  • 重写内部函数,使其调用一个回调,并使用参数来帮助定义回调的。丑陋,hacky,而不是一个受人尊敬的程序员会做的事情。
  • 传递一大堆std::function s的结构。我觉得我可以看到 okay ,但我也觉得它最终会进行堆分配和每次调用间接。
  • 涉及预处理器的一些古怪的东西。天堂得罪了。

1 个答案:

答案 0 :(得分:4)

Deduction guides (C++17)designated initialization (c++20)一起提供了合适的解决方案:

#include <iostream>

template<class F1, class F2>
struct Visitor
{
    F1 fromA;
    F2 fromB;
};
template<class F1, class F2>
Visitor(F1, F2) -> Visitor<F1, F2>;


template<class F1, class F2>
void f(Visitor<F1, F2> v)
{
    v.fromA(1);
    v.fromB("hello");
}


int main()
{
    f( Visitor{
        .fromA = [](int n){ std::cout << "A: " << n << "\n"; },
        .fromB = [](std::string_view v){ std::cout << "B: " << v << "\n"; }
    });
}

demo