альтернатива gsl_function для c ++ - PullRequest
0 голосов
/ 29 июня 2018

Я переключаюсь с C на C ++, и я хотел бы оптимально использовать дополнительные функции, которые становятся доступными, и избегать вещей, которые считаются «в стиле C», таких как void * указатели. В частности, я пытаюсь сделать gsl_function -подобный интерфейс (НЕ обертку для использования gsl в C ++).

В 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(), так что подпрограммам не нужно заботиться о параметрах. Таким образом, вместо макроса FN_EVAL можно использовать operator().

Вещи, которые я пока не мог решить / найти:

  • как я буду хранить параметры. Я думал об использовании шаблона typename. Однако, насколько я понимаю, тогда и сам класс должен быть шаблоном, и в этом случае подпрограммы также должны принимать шаблонный класс. Я не хочу использовать void *.
  • как бы я изменил параметры, которые хранятся в моем class Function. Должен ли я сделать их общедоступными, чтобы их можно было легко изменить, или я должен оставить их закрытыми и написать некоторый интерфейс для доступа к ним? В последнем случае, как я должен идти об этом? Поскольку я буду рассматривать большое разнообразие параметров (как по количеству, так и по типу).

Что нужно учитывать:

  • Я нашел много вопросов и ответов об оболочках для использования gsl-подпрограмм в C ++. Это не то, что я ищу.
  • Я тоже посмотрел на STL <functional>. Насколько я мог понять, он не соответствует моим требованиям к параметрам, которые я могу хранить в функции. Однако я могу ошибаться по этому поводу.
  • Мои функции могут быть довольно сложными (т. Е. Не просто однострочными, как в примере выше), а параметры могут сами содержать смесь двойных чисел, целых, структур ...
  • Я хотел бы иметь возможность расширить Function class на более высокие измерения, то есть f (x, y, z). В С у меня было например

    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 голосов
/ 29 июня 2018

В 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 (это динозавр C ++ от темных веков до лямбда-выражений ). std::function - это очень мощный метод для инкапсуляции, по сути, всего, что может быть вызвано, позволяющее вам реализовать мельчайшие детали вашего кода в хорошо скрытом исходном коде, то есть не нужно раскрывать детали в версии шаблона (которая обязательно должен находиться в заголовочном файле).

Обратите внимание, что, в отличие от ситуации с C, вы можете перегрузить функцию (здесь integrate()), чтобы принимать аргументы разных типов. Здесь я использовал конкретную версию, принимающую std::function, и общую шаблонную версию, принимающую что-либо. Он будет компилироваться до тех пор, пока std::function может быть инициализирован из любого переданного объекта func (однако в случае ошибки сообщение компилятора может быть довольно загадочным).

Эта общность std::function достигается ценой одного косвенного действия, что создает небольшие накладные расходы. Обратите внимание, что эти издержки также присутствуют в вашей реализации C (через указатель Function.p_func). Как правило, эти издержки не являются критическими, но в случае, если это так, вы можете напрямую представить реализацию в виде template в заголовочном файле. Это позволяет избежать накладных расходов, но требует компиляции каждого используемого вами экземпляра (подпрограммы integrate()).

0 голосов
/ 29 июня 2018

Ваш пример в идиоматическом 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 внутренне использует стирание типов, и поэтому есть некоторые издержки. Если здесь есть требования к производительности, то вы можете рассмотреть возможность использования лямбда-функции и сделать 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) });
}

И является более прозрачным для оптимизатора и не требует стирания типа.

...