2013-04-11 2 views
8

долгое время браузер, первый раз здесь. Я написал несколько сценариев для выполнения различных 1D методов численного интегрирования и скомпилировал их в библиотеку. Я бы хотел, чтобы эта библиотека была максимально гибкой в ​​отношении того, что она может интегрировать.C++: функция пропуска с произвольным числом параметров в качестве параметра

Здесь я приведу пример: очень простой пример трапециевидного правила, где я передаю указатель на функцию, которая будет интегрирована.

// Numerically integrate (*f) from a to b 
// using the trapezoidal rule. 
double trap(double (*f)(double), double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
    double xi = a + i*step; 
    if (i == 0 || i == N) { s += (*f)(xi); } 
    else { s += 2*(*f)(xi); } 
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Это прекрасно работает для простых функций, которые принимают только один аргумент. Пример:

double a = trap(sin,0,1); 

Однако иногда может потребоваться интегрировать то, что имеет больше параметров, как квадратичный полином. В этом примере коэффициенты будут определяться пользователем перед интеграцией. Пример кода:

// arbitrary quadratic polynomial 
double quad(double A, double B, double C, double x) { 
    return (A*pow(x,2) + B*x + C); 
} 

В идеале, я бы смог сделать что-то вроде этого, чтобы интегрировать его:

double b = trap(quad(1,2,3),0,1); 

Но ясно, что не работает. Я получил эту проблему, определив класс, который имеет коэффициенты в качестве членов и функции интереса как функции члена:

class Model { 
    double A,B,C; 
public: 
    Model() { A = 0; B = 0; C = 0; } 
    Model(double x, double y, double z) { A = x; B = y; C = z; } 
    double func(double x) { return (A*pow(x,2)+B*x+C); } 
}; 

Однако тогда моя функция интеграции необходимо изменить, чтобы взять объект в качестве входных данных вместо указатель функции:

// Numerically integrate model.func from a to b 
// using the trapezoidal rule. 
double trap(Model poly, double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
    double xi = a + i*step; 
    if (i == 0 || i == N) { s += poly.func(xi); } 
    else { s += 2*poly.func(xi); } 
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Это прекрасно работает, но в результате библиотека не очень независимы, так как она нуждается в классе модели быть определен где-то. Кроме того, в идеале Модель должна быть способна меняться от пользователя к пользователю, поэтому я не хочу исправлять ее в файле заголовка. Я попытался использовать функциональные шаблоны и функторы, чтобы заставить это работать, но он не очень независим, так как снова шаблон должен быть определен в файле заголовка (если вы не хотите явно создать экземпляр, которого я не знаю).

Итак, подытожим: есть ли способ, каким образом я могу получить свои интеграционные функции для принятия произвольных 1D-функций с переменным количеством входных параметров, при этом все еще оставаясь независимым, чтобы их можно было скомпилировать в автономную библиотеку? Заранее благодарим за предложения.

+0

['std :: bind'] (http://en.cppreference.com/w/cpp/utility/functional/bind) –

ответ

8

Вам нужны шаблоны и std::bind() (или его boost::bind(), если вы не можете позволить себе C++ 11). Например, это то, что ваша trap() функция стала бы:

template<typename F> 
double trap(F&& f, double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
    double xi = a + i*step; 
    if (i == 0 || i == N) { s += f(xi); } 
//        ^
    else { s += 2* f(xi); } 
//    ^
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Обратите внимание, что мы обобщающих из указателей на функции и позволяют любой тип ГОО объектов (включая лямбда в C++ 11, например), чтобы поэтому синтаксис для вызова предоставляемой пользователем функции не является *f(param) (который работает только для указателей функций), а только f(param).

Что касается гибкости, давайте рассмотрим два жёстко прописанные функции (и делать вид, чтобы они были значимыми):

double foo(double x) 
{ 
    return x * 2; 
} 

double bar(double x, double y, double z, double t) 
{ 
    return x + y * (z - t); 
} 

Теперь вы можете обеспечить как первую функцию непосредственно на входе в trap(), или в результате связывания последнего три аргумента второй функции в некоторую определенную величину (у вас есть свободный выбор, на которых аргументы для связывания):

#include <functional> 

int main() 
{ 
    trap(foo, 0, 42); 
    trap(std::bind(bar, std::placeholders::_1, 42, 1729, 0), 0, 42); 
} 

конечно, вы можете получить еще большую гибкость с лямбдой:

#include <functional> 
#include <iostream> 

int main() 
{ 
    trap(foo, 0, 42); 
    trap(std::bind(bar, std::placeholders::_1, 42, 1729, 0), 0, 42); 

    int x = 1729; // Or the result of some computation... 
    int y = 42; // Or some particular state information... 
    trap([&] (double d) -> double 
    { 
     x += 42 * d; // Or some meaningful computation... 
     y = 1; // Or some meaningful operation... 
     return x; 
    }, 0, 42); 

    std::cout << y; // Prints 1 
} 

И вы можете также передать свои собственные отслеживанием состояния функторы ф trap(), или некоторые вызываемые объекты, завернутые в std::function объекта (или boost::function, если вы не можете позволить себе C++ 11). Выбор довольно широк.

Вот live example.

+0

Спасибо! Мне нравится пример шаблона, потому что он очень изящный, но я не уверен, что он будет работать для меня - я понимаю, что функция шаблона должна быть определена и объявлена ​​в одном и том же месте, в то время как я хотел бы определить 'trap () 'в отдельной библиотеке, а затем объявить ее для использования в других проектах. Я принял ваше предложение использовать 'std :: function' в сочетании с' std :: bind' и переопределил 'trap()' как 'двойную ловушку (std :: function f, double a, double b) '; единственной досадной частью является то, что я должен переопределить простые функции, такие как sin, как 'std :: function'. – t354

+2

@ t354: Да, шаблоны страдают от этой проблемы разделения: определение должно быть в файлах заголовков. Кроме того, вы * можете * поместить определения в файл '.cpp' и предоставить так называемые * явные экземпляры * вашего шаблона для всех возможных аргументов, используемых в вашем приложении, но если ваша библиотека является, то невозможно предскажите, как будет создан ваш шаблон. Ввод 'std :: function' в подпись - это нормально: имейте в виду, что накладные расходы времени выполнения, поэтому вам придется измерять, является ли это релевантными накладными расходами для вас или нет. –

+1

@ t354: Я не понимаю последнюю часть, хотя: что вы подразумеваете под «переопределить простые функции, такие как« грех »? Если вы имеете в виду это: 'std :: function = sin; trap (sin, 0, 42); ', чем это не нужно. Вы можете напрямую вызвать 'trap (sin, 0, 42);', не объявляя объект 'std :: function'. –

3

То, что вы пытаетесь сделать, это сделать это возможный

trap(quad, 1, 2, 3, 0, 1); 

С C++ 11 есть шаблон псевдонима и VARIADIC шаблон

template< typename... Ts > 
using custom_function_t = double (*f) (double, Ts...); 

выше определение custom_function_t, которые принимают двойные и переменные количество аргументов.

поэтому ваша trap функция становится

template< typename... Ts > 
double trap(custom_function_t<Ts...> f, Ts... args, double a, double b) { 
    int N = 10000; 
    double step = (b-a)/N; 
    double s = 0; 
    for (int i=0; i<=N; i++) { 
     double xi = a + i*step; 
     if (i == 0 || i == N) { s += f(xi, args...); } 
     else { s += 2*f(xi, args...); } 
    } 
    s *= (b-a)/(2*N); 
    return s; 
} 

Использование:

double foo (double X) { 
    return X; 
} 

double quad(double X, double A, double B, double C) { 
    return(A*pow(x,2) + B*x + C); 
} 

int main() { 
    double result_foo = trap(foo, 0, 1); 
    double result_quad = trap(quad, 1, 2, 3, 0, 1); // 1, 2, 3 == A, B, C respectively 
} 

Проверено на Apple, LLVM 4.2 компилятором.

+0

Это просто смущает –

+0

@MooingDuck Я согласен ... – yngccc

Смежные вопросы