gsl_function c ++的替代方案

时间:2018-06-29 09:39:59

标签: c++ c c++11 gsl

我正在从C切换到C ++,并且我想最佳地使用可用的其他功能,并避免使用诸如void *指针之类的“ C风格”的东西。具体来说,我正在尝试创建一个类似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结构的实例,该实例包含一个指向某些函数参数的指针(通常包含在另一个结构中)和一个指向“正常” C的指针。功能。这样做的好处是,使用Function的例程仅需要知道它是一个函数,该函数需要double并返回double(它们使用FN_EVAL宏)。他们不必关心参数的类型和数量。另一方面,我可以轻松地从例程外部更改存储在参数中的值。

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

我尚未决定/发现的事情是:

  • 我将如何存储参数。我正在考虑使用模板类型名。但是,据我所知,类本身也必须是模板,在这种情况下,例程也应接受模板化的类。我不想使用void *
  • 我将如何更改存储在class Function中的参数。我应该公开它们以便可以轻松更改它们,还是应该保持它们私有并编写一些访问它们的界面?在后一种情况下,我该如何处理?因为我将考虑各种各样的参数(无论是数量还是类型)​​。

注意事项:

  • 我发现了很多有关在C ++中使用gsl例程的包装器的问题和答案。那不是我想要的。
  • 我还查看了STL <functional>。据我所知,它不能满足我对可以存储在函数中的参数的要求。但是,我对此可能是错的。
  • 我的函数可能非常复杂(即,不仅仅是上面示例中的单行代码),并且参数本身可以包含双精度,整数,结构...
  • 我想有可能将Function class扩展到更高的维度,即f(x,y,z)。例如,在C语言中,

    struct FunctionThree_struct
    {
      double (* p_func) (double x, double y, double z, void * p_params);
      void * p_params;
    };
    typedef struct FunctionThree_struct FunctionThree;
    #define FN_THREE_EVAL(p_F,x,y,z) (*((p_F)->p_func))(x,y,z,(p_F)->p_params)
    
  • 我关心有效的函数调用。尤其对于高维积分,这些函数将被调用数百万次。

2 个答案:

答案 0 :(得分:4)

您在惯用C ++ 11中使用std::function / std::bind的示例可能类似于以下内容:

#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) });
}

对优化程序更透明,不涉及类型擦除。

答案 1 :(得分:1)

在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()例程)。