2013-04-11 15 views
8

długa przeglądarka, tutaj pierwszy pytający. Napisałem wiele skryptów do wykonywania różnych metod integracji numerycznej 1D i skompilowałem je do biblioteki. Chciałbym, aby ta biblioteka była jak najbardziej elastyczna w odniesieniu do tego, co jest w stanie zintegrować.C++: funkcja pass z dowolną liczbą parametrów jako parametr

Oto przykład: bardzo prosty przykład reguły trapezowej, w której przekazuję wskaźnik do funkcji, która ma być zintegrowana.

// 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; 
} 

Działa to doskonale w przypadku prostych funkcji, które przyjmują tylko jeden argument. Przykład:

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

Czasami jednak mogę chcieć zintegrować coś, co ma więcej parametrów, na przykład wielomian kwadratowy. W tym przykładzie współczynniki będą definiowane przez użytkownika przed integracją. Przykładowy kod:

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

Idealnie, byłbym w stanie zrobić coś takiego, żeby go zintegrować:

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

jednak wyraźnie, że nie działa. Dostałem ten problem poprzez zdefiniowanie klasy, która ma współczynniki jako członków i funkcji zainteresowania w funkcji użytkownika:

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

Jednak wtedy moja funkcja integracja musi się zmienić, aby wziąć przedmiot jako wejście zamiast wskaźnik funkcji:

// 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; 
} 

Działa to dobrze, ale powstała biblioteka nie jest bardzo niezależna, ponieważ potrzebuje gdzieś zdefiniować model klasy. Również model powinien być w stanie zmienić się z użytkownika na użytkownika, więc nie chciałbym go naprawiać w pliku nagłówkowym. Próbowałem użyć szablonów funkcyjnych i funktorów, aby to działało, ale nie jest ono bardzo niezależne, ponieważ szablon powinien być zdefiniowany w pliku nagłówkowym (chyba, że ​​chcesz jawnie utworzyć instancję, której nie robię).

Podsumowując: czy istnieje sposób, w jaki mogę uzyskać funkcje integracji, aby zaakceptować dowolne funkcje 1D ze zmienną liczbą parametrów wejściowych, pozostając jednocześnie na tyle niezależne, aby można je było skompilować w samodzielną bibliotekę? Z góry dziękuję za sugestie.

+0

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

Odpowiedz

8

Potrzebne są szablony i std::bind() (lub jego odpowiednik boost::bind(), jeśli nie stać Cię na C++ 11). Na przykład, to jest to, co stanie się funkcja 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; 
} 

Zauważmy, że jesteśmy uogólniając ze wskaźników funkcji i pozwalają wszelkiego rodzaju obiektówpłatnych na żądanie (w tym C++ 11 lambda, na przykład) do zostać przekazana. Dlatego składnia wywołania funkcji dostarczanej przez użytkownika nie jest *f(param) (która działa tylko dla wskaźników funkcji), ale po prostu f(param).

chodzi o elastyczność, rozważmy dwie funkcje jawnie wpisanej (i udawać, że je za sensowne):

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

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

Teraz można zapewnić zarówno pierwszą funkcję bezpośrednio na wejściu do trap() lub skutkiem wiążącym ostatni trzy argumenty drugiej funkcji do jakiejś konkretnej wartości (masz wolny wybór, w którym argumenty do wiązania):

#include <functional> 

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

oczywiście, można uzyskać jeszcze większą elastyczność przy lambdas:

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

I można również przekazać własne stanowe funktory tp trap() lub niektóre wywoływalne przedmioty zawinięte w obiekcie std::function (lub boost::function jeśli nie może sobie pozwolić C++ 11). Wybór jest dość szeroki.

Oto live example.

+0

Dzięki! Podobał mi się przykład szablonu, ponieważ jest bardzo elegancki, ale nie jestem pewien, czy będzie on działał dla mnie - moim zdaniem jest to, że funkcja szablonu musi być zdefiniowana i zadeklarowana w tym samym miejscu, podczas gdy ja chciałbym zdefiniować 'pułapkę () 'w oddzielnej bibliotece, a następnie zadeklaruj ją do użycia w innych projektach. Przyjąłem twoją sugestię użycia 'std :: function' w połączeniu z' std :: bind' i przedefiniowałem 'trap()' jako 'podwójna pułapka (std :: function f, double a, double b) '; Jedyną irytującą częścią jest to, że muszę przedefiniować proste funkcje, takie jak sin, jako 'std :: function'. – t354

+2

@ t354: Tak, szablony cierpią z powodu tego problemu separacji: definicja musi znajdować się w plikach nagłówkowych. Ewentualnie możesz * umieścić definicje w pliku '.cpp' i podać tzw. * Instancje jawne * twojego szablonu dla wszystkich możliwych argumentów używanych w twojej aplikacji, ale jeśli twoja jest biblioteką, to niemożliwe jest przewidzieć, jak będzie tworzony twój szablon. Umieszczenie 'std :: function' w podpisie jest w porządku: należy pamiętać, że istnieje nakład czasu, więc będziesz musiał zmierzyć, czy jest to istotne obciążenie dla Ciebie, czy nie. –

+1

@ t354: Nie rozumiem jednak ostatniej części: co rozumiesz przez "przedefiniowanie prostych funkcji, takich jak" grzech "? Jeśli masz na myśli to: 'std :: function = sin; pułapka (sin, 0, 42); ', niż nie jest potrzebna. Możesz wywołać bezpośrednio 'trap (sin, 0, 42);', bez deklarowania obiektu 'std :: function'. –

3

Co chcesz zrobić jest aby było to możliwe

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

z C++ 11 mamy szablon alias i zmiennej liczbie argumentów szablonu

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

powyżej zdefiniować custom_function_t że biorą zmienną podwójne i liczba argumentów.

więc funkcja trap staje

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

Zastosowanie:

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 
} 

Testowane na jabłko LLVM 4.2 kompilatora.

+0

To tylko mylące –

+0

@MooingDuck Zgadzam się ... – yngccc

Powiązane problemy