gsl_function c++ 的替代品

gsl_function alternative for c++

我正在从 C 切换到 C++,我想以最佳方式使用可用的附加功能,并避免考虑 'C-style' 的东西,例如 void * 指针。具体来说,我正在尝试制作一个类似于 gsl_function 的界面(不是在 C++ 中使用 gsl 的包装器)。

在 C 中,我编写了几个用于求根、积分...的例程,它们使用类似 gsl_function 的接口将数学函数传递给这些例程。该界面如下所示:

struct Function_struct
{
  double (* p_func) (double x, void * p_params);
  void * p_params;
};
typedef struct Function_struct Function;

#define FN_EVAL(p_F,x) (*((p_F)->p_func))(x,(p_F)->p_params)

并且可以通过以下方式使用:

struct FuncParams_struct { double a; double b; double c; };

double my_sqrt(double x, void * p) {
    struct FuncParams_struct * p_params = (struct FuncParams_struct *) p;
    double a = p_params->a;
    double b = p_params->b;
    double c = p_params->c;

    return a/sqrt(x+b)+c;
}

Function My_Sqrt;
My_Sqrt.p_func = &my_sqrt;
struct FuncParams_struct my_params = { 1.0, 1.0, 0.0 };
My_Sqrt.p_params = &my_params;

// Call the function at a certain x
double result_at_3 = FN_EVAL(&My_Sqrt, 3);

// Pass My_Sqrt to an integration routine 
// (which does not care about the parameters a,b,c 
// and which can take any other Function to integrate)
// It uses FN_EVAL to evaluate MySqrt at a certain x.
double integral = integrate(&My_Sqrt);

// Easily change some of the parameters
My_Sqrt.p_params->a=3.0;

如您所见,这允许我创建 Function 结构的实例,它包含指向某些函数参数(通常包含在另一个结构中)的指针和指向 'normal' C 函数。这样做的好处在于,使用 Function 的例程只需要知道它是一个接受双精度和 returns 一个双精度的函数(他们使用 FN_EVAL 宏)。他们不必关心参数的类型和数量。另一方面,我可以从例程外部轻松更改存储在参数中的值。

我现在想在 C++ 函数中突出显示上面的功能。我搜索了很多关于什么是获得它的最佳方法,但我找不到它(部分原因是与 C 相比,我对 C++ 的可能性有点不知所措)。到目前为止,我一直在考虑制作 class Function。这个 class 可以存储实际的函数定义,并且可以通过定义 operator() 使其可调用,这样例程就不必再关心参数了。因此可以使用 operator() 代替 FN_EVAL 宏。

我还不能decide/find的事情是:

需要考虑的事项:

您使用 std::function/std::bind 的惯用 C++11 示例可能如下所示:

#include <functional>

double sqrt_fn(double x, double a, double b, double c);
double integrate(double x_0, double x_1, std::function<double(double)>);

void foo() {
    // Get parameters a, b, c
    double a = ..., b = ..., c = ...;
    double x_0 = ..., x_1 = ...;
    double integral = integrate(x_0, x_1, std::bind(&sqrt_fn, std::placeholders::_1, a, b, c));
}

这很笼统,但是 std::function 内部使用了类型擦除,因此有一些开销。如果这里有性能需求,那么可以考虑使用lambda函数,将integrate做成接受泛型Callable的模板函数。这看起来像:

double sqrt_fn(double x, double a, double b, double c);
template <typename Func>
double integrate(double x_0, double x_1, Func f);

void foo() {
    // Get parameters a, b, c
    double a = ..., b = ..., c = ...;
    double x_0 = ..., x_1 = ...;
    double integral = integrate(x_0, x_1, [a, b, c](double x) { return sqrt_fn(x, a, b, c) });
}

并且对优化器更透明并且不涉及类型擦除。

在 C++ 中,您可以使用模板和 std::function 完成所有这些工作。例如:

// your header file
#include <functional>
namespace myNumerics {
    // version taking std::function argument, implemented in source
    double integrate(double a, double b, std::function<double(double)> const&func,
                     double err=1.e-9);

    // template version taking any callable object, including function pointers
    template<typename Func>
    inline double integrate(double a, double b, Func const&func, double err=1.e-9)
    {
        return integrate(a,b,std::function<double(double)>(func),err);
    }
}

// your source code, implements the numerics
double myNumerics::integrate(double a, double b, 
                             std::function<double(double)> const&func,
                             double err)
{
    some clever code here (but do not re-invent the wheel again);
}

// application code, uses the template version
void application(double a, double b, double c)
{
    // ...
    auto f = myNumerics::integrate(0., 1., [=](double x) { return a/sqrt(x+b)+c; });
    // ...
}

特别是,不要理会 std::bind(这是 lambda expressions 之前黑暗时代的 C++ 恐龙)。 std::function 是一种非常强大的方法,它基本上封装了任何可以调用的东西,允许您在隐藏良好的源代码中实现代码的基本细节,即无需在模板版本中公开细节(必须驻留在头文件中)。

另请注意,与 C 的情况不同,您可以重载函数(此处 integrate())以采用不同类型的参数。在这里,我使用了一个采用 std::function 的特定版本和一个采用任何东西的通用模板版本。只要 std::function 可以从传递的任何 func 对象进行初始化,它就会编译(不过,如果出现错误,编译器消息可能会相当含糊)。

std::function 的这种普遍性是以一次间接为代价的,产生了很小的开销。但是请注意,这种开销也存在于您的 C 实现中(通过 Function.p_func 指针)。通常,这种开销并不重要,但如果确实如此,您可以在头文件中将实现直接公开为 template。这避免了开销,但需要编译您使用的每个实例(integrate() 例程)。