C++:使用任意数量的参数传递函数作为参数

t35*_*354 9 c++ templates function-pointers functor

很长一段时间的浏览器,第一次问问这里.我编写了许多用于执行各种一维数值积分方法的脚本,并将它们编译成一个库.我希望该库尽可能灵活地集成它的功能.

这里我举一个例子:一个非常简单的梯形规则示例,我将指针传递给要集成的函数.

// Numerically integrate (*f) from a to b
// using the trapezoidal rule.
double trap(double (*f)(double), double a, double b) {
  int N = 10000;
  double step = (b-a)/N;
  double s = 0;
  for (int i=0; i<=N; i++) {
    double xi = a + i*step;
    if (i == 0 || i == N) { s += (*f)(xi); }
    else { s += 2*(*f)(xi); }
  }
  s *= (b-a)/(2*N);
  return s;
}
Run Code Online (Sandbox Code Playgroud)

这适用于只接受一个参数的简单函数.例:

double a = trap(sin,0,1);
Run Code Online (Sandbox Code Playgroud)

但是,有时我可能想要集成具有更多参数的东西,例如二次多项式.在该示例中,系数将在集成之前由用户定义.示例代码:

// arbitrary quadratic polynomial
double quad(double A, double B, double C, double x) {
  return (A*pow(x,2) + B*x + C);
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我可以做这样的事情来集成它:

double b = trap(quad(1,2,3),0,1);
Run Code Online (Sandbox Code Playgroud)

但显然这不起作用.我通过定义一个具有系数作为成员和感兴趣的函数作为成员函数的类来解决这个问题:

class Model {
  double A,B,C;
public:
  Model() { A = 0; B = 0; C = 0; }
  Model(double x, double y, double z) { A = x; B = y; C = z; }
  double func(double x) { return (A*pow(x,2)+B*x+C); }
};
Run Code Online (Sandbox Code Playgroud)

但是,然后我的集成函数需要更改为将对象作为输入而不是函数指针:

// Numerically integrate model.func from a to b
// using the trapezoidal rule.
double trap(Model poly, double a, double b) {
  int N = 10000;
  double step = (b-a)/N;
  double s = 0;
  for (int i=0; i<=N; i++) {
    double xi = a + i*step;
    if (i == 0 || i == N) { s += poly.func(xi); }
    else { s += 2*poly.func(xi); }
  }
  s *= (b-a)/(2*N);
  return s;
}
Run Code Online (Sandbox Code Playgroud)

这样工作正常,但结果库不是非常独立,因为它需要在某处定义类Model.此外,理想情况下,模型应该能够从用户更改为用户,因此我不想在头文件中修复它.我试图使用函数模板和仿函数来使其工作,但它不是非常独立,因为再次,模板应该在头文件中定义(除非你想显式实例化,我不这样做).

总而言之:有没有什么方法可以让我的集成函数接受具有可变数量输入参数的任意1D函数,同时仍然保持足够独立以便可以将它们编译成独立的库?提前感谢您的建议.

And*_*owl 8

你需要的是模板和std::bind()(或者boost::bind()如果你买不起C++ 11的话).例如,这就是你的trap()功能将成为:

template<typename F>
double trap(F&& f, double a, double b) {
  int N = 10000;
  double step = (b-a)/N;
  double s = 0;
  for (int i=0; i<=N; i++) {
    double xi = a + i*step;
    if (i == 0 || i == N) { s += f(xi); }
//                               ^
    else { s += 2* f(xi); }
//                 ^
  }
  s *= (b-a)/(2*N);
  return s;
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们从函数指针推广并允许传入任何类型的可调用对象(例如,包括C++ 11 lambda).因此,调用用户提供的函数的语法不是*f(param)(仅适用于对于函数指针),但只是f(param).

关于灵活性,让我们考虑两个硬编码函数(并假装它们有意义):

double foo(double x)
{
    return x * 2;
}

double bar(double x, double y, double z, double t)
{
    return x + y * (z - t);
}
Run Code Online (Sandbox Code Playgroud)

您现在可以直接在输入中提供第一个函数trap(),或者将第二个函数的最后三个参数绑定到某个特定值的结果(您可以自由选择要绑定的参数):

#include <functional>

int main()
{
    trap(foo, 0, 42);
    trap(std::bind(bar, std::placeholders::_1, 42, 1729, 0), 0, 42);
}
Run Code Online (Sandbox Code Playgroud)

当然,使用lambdas可以获得更大的灵活性:

#include <functional>
#include <iostream>

int main()
{
    trap(foo, 0, 42);
    trap(std::bind(bar, std::placeholders::_1, 42, 1729, 0), 0, 42);

    int x = 1729; // Or the result of some computation...
    int y = 42; // Or some particular state information...
    trap([&] (double d) -> double
    {
        x += 42 * d; // Or some meaningful computation...
        y = 1; // Or some meaningful operation...
        return x;
    }, 0, 42);

    std::cout << y; // Prints 1
}
Run Code Online (Sandbox Code Playgroud)

你也可以传递你自己的有状态函子tp trap(),或者包含在一个std::function对象中的一些可调用对象(或者boost::function如果你买不起C++ 11).选择范围很广.

这是一个实例.

  • @ t354:是的,模板遇到了这个分离问题:定义必须在头文件中.或者,您可以*将定义放在`.cpp`文件中,并为您的应用程序中使用的所有可能参数提供模板的所谓*显式实例化*,但如果您的库是库,则不可能预测模板的实例化方式.将`std :: function`放在签名中是可以的:请注意存在运行时开销,因此您必须测量它是否是相关的开销. (2认同)
  • @ t354:哦,那是因为[`std :: sin`](http://en.cppreference.com/w/cpp/numeric/math/sin)实际上是一个重载函数(有一个`float`版本,一个`double`版本等等,所以当你写'sin`时,编译器不能告诉你想要它选择什么重载.所以你应该提供一个显式的强制转换:`trap(static_cast <double(*)(double)>(sin),0,42);` (2认同)