2013-04-11 13 views
8

lange Zeit Browser, erste Zeit hier fragen. Ich habe eine Reihe von Skripten für verschiedene numerische 1D-Integrationsmethoden geschrieben und diese in eine Bibliothek kompiliert. Ich möchte, dass diese Bibliothek so flexibel wie möglich ist in Bezug darauf, was sie integrieren kann.C++: Funktion übergeben mit einer beliebigen Anzahl von Parametern als Parameter

Hier ein Beispiel: ein sehr einfaches trapezförmiges Regelbeispiel, bei dem ich einen Zeiger auf die zu integrierende Funktion übergebe.

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

Dies funktioniert gut für einfache Funktionen, die nur ein Argument. Beispiel:

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

Aber manchmal kann ich möchte etwas integrieren, die weitere Parameter, wie ein quadratisches Polynom hat. In diesem Beispiel würden die Koeffizienten vor der Integration vom Benutzer definiert werden. Beispielcode:

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

Idealerweise würde ich in der Lage sein, so etwas zu tun, um es zu integrieren:

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

Aber klar, das nicht funktioniert. Ich habe durch die Definition einer Klasse, um dieses Problem bekommen, dass die Koeffizienten als Mitglieder und die Funktion von Interesse als Mitglied Funktion hat:

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

jedoch dann meine Integrationsfunktion ein Objekt als Eingabe nehmen ändern muss statt ein Funktionszeiger:

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

Dies funktioniert gut, aber die resultierende Bibliothek ist nicht sehr unabhängig, da es die Klasse Modell definiert werden, irgendwo muss. Außerdem sollte das Model im Idealfall von Benutzer zu Benutzer wechseln können, so dass ich es nicht in einer Header-Datei reparieren möchte. Ich habe versucht, Funktionsvorlagen und Funktoren zu verwenden, um dies zum Funktionieren zu bringen, aber es ist nicht sehr unabhängig, da wieder die Vorlage in einer Header-Datei definiert werden sollte (es sei denn, Sie möchten explizit instanziieren, was ich nicht).

Also zusammenfassend: Gibt es irgendeinen Weg, wie ich meine Integrationsfunktionen dazu bringen kann, beliebige 1D-Funktionen mit einer variablen Anzahl von Eingabeparametern zu akzeptieren, während sie trotzdem unabhängig genug bleiben, dass sie in eine eigenständige Bibliothek kompiliert werden können? Vielen Dank im Voraus für die Vorschläge.

+0

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

Antwort

8

Was Sie brauchen, ist Vorlagen und std::bind() (oder sein boost::bind() Gegenstück, wenn Sie C++ 11 nicht leisten können). Zum Beispiel ist es das, was Ihre trap() Funktion würde:

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

Hinweis, dass wir von Funktionszeigern sind verallgemeinern und erlaubt jede Art von aufrufbare Objekten (einschließlich C++ 11 Lambda, zum Beispiel) zu übergeben werden. Daher ist die Syntax zum Aufrufen der vom Benutzer bereitgestellten Funktion nicht *f(param) (was nur für Funktionszeiger funktioniert), sondern nur f(param).

In Bezug auf die Flexibilität, lassen Sie sich zwei hartcodierte Funktionen betrachtet (und so tun, sie sinnvoll sein):

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

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

Sie nun sowohl die erste Funktion direkt am Eingang trap(), oder das Ergebnis der Bindung des letzten zur Verfügung stellen kann drei Argumente der zweiten Funktion zu einem bestimmten Wert (haben Sie die freie Wahl, auf die Argumente zu binden):

#include <functional> 

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

natürlich können Sie noch mehr Flexibilität mit Lambda-Ausdrücke erhalten:

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

Und Sie können auch Ihre eigenen Stateful functors tp trap() oder einige aufrufbare Objekte in einem std::function Gegenstand gewickelt passieren (oder boost::function wenn Sie C++ 11 nicht leisten können). Die Auswahl ist ziemlich breit.

Hier ist ein live example.

+0

Vielen Dank! Ich mag das Template-Beispiel, weil es sehr elegant ist, aber ich bin mir nicht sicher, ob es für mich funktionieren wird. Ich verstehe, dass eine Template-Funktion an derselben Stelle definiert und deklariert werden muss.) 'in einer separaten Bibliothek und deklarieren Sie sie dann zur Verwendung in anderen Projekten. Ich nahm Ihren Vorschlag, 'std :: function' in Verbindung mit' std :: bind' zu verwenden und definierte 'trap()' als 'double trap (std :: function f, double a, double b) '; Der einzige nervige Teil ist, dass ich einfache Funktionen wie sin als 'std :: function' neu definieren muss. – t354

+2

@ t354: Ja, Vorlagen leiden unter diesem Trennungsproblem: Die Definition muss in Header-Dateien sein. Alternativ könnten Sie * die Definitionen in eine '.cpp'-Datei einfügen und so genannte * explizite Instanziierungen * Ihrer Vorlage für alle möglichen Argumente bereitstellen, die in Ihrer Anwendung verwendet werden, aber wenn Ihre Bibliothek eine Bibliothek ist, ist es unmöglich vorhersagen, wie Ihre Vorlage instanziiert wird. Das Setzen von 'std :: function' in die Signatur ist in Ordnung: Beachten Sie, dass es einen Laufzeit-Overhead gibt, also müssen Sie messen, ob es sich um einen relevanten Overhead für Sie handelt oder nicht. –

+1

@ t354: Ich verstehe den letzten Teil nicht: was meinst du mit "einfache Funktionen wie' sin' neu definieren? " Wenn du das meinst: 'std :: function = sin; Falle (Sünde, 0, 42); ', als es nicht gebraucht wird. Sie können direkt 'trap (sin, 0, 42);' aufrufen, ohne das Objekt 'std :: function' zu deklarieren. –

3

Was wollen Sie zu tun ist dies möglich

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

Mit C++ 11 haben wir alias Vorlage und variadische Vorlage

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

oben machen definieren ein custom_function_t, die einen doppelten und variable nehmen Anzahl der Argumente.

so Ihre trap Funktion wird

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

Verbrauch:

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 
} 

Getestet auf Apple LLVM 4.2 Compiler.

+0

Das ist einfach verwirrend –

+0

@MooingDuck Ich stimme zu ... – yngccc

Verwandte Themen