我正在从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
中的参数。我应该公开它们以便可以轻松更改它们,还是应该保持它们私有并编写一些访问它们的界面?在后一种情况下,我该如何处理?因为我将考虑各种各样的参数(无论是数量还是类型)。注意事项:
<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)
我关心有效的函数调用。尤其对于高维积分,这些函数将被调用数百万次。
答案 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()
例程)。