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的事情是:
- 我将如何存储参数。我正在考虑使用模板类型名。然而,据我所知,class 本身也需要是一个模板,在这种情况下,例程也应该接受模板化的 class。我不想使用
void *
.
- 我将如何更改存储在
class Function
中的参数。我应该让它们 public 以便我可以轻松更改它们,还是应该将它们保密并编写一些接口来访问它们?在后一种情况下,我应该怎么做?因为我将考虑各种各样的参数(数量和类型)。
需要考虑的事项:
- 我发现了很多关于在 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)
我确实关心高效的函数调用。特别是对于更高维的积分,函数将被调用数百万次。
您使用 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()
例程)。
我正在从 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的事情是:
- 我将如何存储参数。我正在考虑使用模板类型名。然而,据我所知,class 本身也需要是一个模板,在这种情况下,例程也应该接受模板化的 class。我不想使用
void *
. - 我将如何更改存储在
class Function
中的参数。我应该让它们 public 以便我可以轻松更改它们,还是应该将它们保密并编写一些接口来访问它们?在后一种情况下,我应该怎么做?因为我将考虑各种各样的参数(数量和类型)。
需要考虑的事项:
- 我发现了很多关于在 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)
我确实关心高效的函数调用。特别是对于更高维的积分,函数将被调用数百万次。
您使用 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()
例程)。