Zeiger auf gebundene Funktion als Funktionsparameter



  • Hallo Leute, erst mal ein kleines Hallo an alle! Das hier ist mein erster Beitrag 🙂

    So, nun zum Problem:
    Wie kann kann ich den Zeiger auf eine gebundene Funktion als Funktionsparameter übergeben?

    Versuche ich das nämlich, so meckert Visual Studio 2010:

    Ein Zeiger auf eine gebundene Funktion darf nur zum Aufrufen der Funktion verwendet werden.

    Was genau bedeutet es und wie kann ich es umgehen?

    Das Ziel, welches ich damit verfolge, ist der Runge-Kutta-Methode eine Funktion aus einer Klasse zu übergeben. Diese Funktion berechnet die Ableitungen und RungeKutta damit dann den nächsten Punkt der Trajektorie.

    Runge-Kutte-Methode:

    void rk4(int n, double *y, double *f, double x, double h, void (*derivs) (double,double [], double [])){
    ...
    }
    

    Klasse mit ODE-Funktion, in der die Ableitungen berechnet werden:

    class MyClass {
     // parameter
    
     void ODE(double x, double *y, double *f) {...}
    };
    

    Nicht funktionierende Übergabe des Zeigers:

    MyClass a(...);
    
    rk(n,y,f,x,h,(&a)->*ODE)
    ...
    

    Hier ist es nun so, dass der Ausdruck "*(&a)->ODE" nicht akzeptiert wird und der obere Hinweis ausgegeben wird.

    Meine Suche nach diesem Problem hat mich nicht wirklich weiter gebracht. Wenn ich es richtig verstanden habe, dann hat es etwas mit dem impliziten this-pointer zu tun. Eine Lösung konnte ich aber nicht finden. Ich hoffe meine Fragestellung ist klar formuliert. Wenn nicht, dann fragt bitte nach.



  • Das Problem ist, dass du versuchst eine Memberfunktion zu übergeben, allerdings sind Memberfunktionen verschieden von normalen, sie brauchen immer ein Objekt, mit dem sie aufgerufen werden können. Um dein Problem zu lösen gibt es jetzt folgende Möglichkeiten:

    1. Du machst deine Funktion static. Statische Funktionen verhalten sich wie normale Funktionen, man kann sie direkt übergeben, nämlich mit der Syntax &Class::method.

    2. Du benutzt Funktoren. Mit bind kannst du folgendes schreiben:

    class MyClass
    {
        void sayHello() { std::cout << "Hello World!" << std::endl;
    };
    
    int main()
    {
        MyClass obj;
        std::function<void()> f = std::bind(&MyClass::sayHello, &obj);
        f();
    }
    

    f ist jetzt ein Funktionsobjekt, das für dich die Methode sayHello() auf dem Objekt obj aufruft.

    Generell würde ich anstatt Funktionszeigern immer Funktoren verwenden, da du so viel flexibler bist. Funktoren geistern irgendwo im tr1 rum. Da ich einen C++0x Compiler verwende, habe ich sie im Header <functional>. Boost bietet auch Funktoren sowie bind.

    Übrigens haben die beiden Ausdrücke obj.*func und obj_ptr->*func keinen greifbaren Typ. Deshalb kannst du damit auch nichts anfangen.



  • Viele Wege führen nach Rom.

    Beispiel: Generisch (Templates) + Lambdas

    template<class Func>
    void rk4(???, Func f)
    {
      ...
      ??? f(???);
      ...
    }
    
    class konkretes_problem {
      ...
      ??? eval_f(???) const;
      ...
    };
    
       ...
       konkretes_problem kp;
       rk4(???, [&kp](???){return kp.eval_f(???);} );
    

    Statt der Lambda-Funktion (welches noch nicht offizieller Bestandteil von C++ ist) kannst Du hier auch einen eigenen Funktor oder std::tr1::bind oder boost::bind benutzen.

    Beispiel: "Laufzeit-Polymorphie":

    class abstract_gradient_field
    {
    protected:
      ~abstract_gradient_field() {}
    public:
      virtual ??? evaluate(???) const = 0;
    };
    
    void rk4(???, abstract_gradient_field const& af)
    {
      ...
      ??? af.evaluate(???);
      ...
    }
    
    class konkretes_problem : public abstract_gradient_field {
      ...
      ??? evaluate(???) const;
      ...
    };
    
       ...
       konkretes_problem kp;
       rk4(???,kp);
    

    Dies ist für Einsteiger vielleicht erstmla einfacher zu verstehen. Der generische Ansatz von oben ist aber mächtiger/flexibler und ggf performanter.



  • Vielen Dank für die schnellen Antworten. Das Problem habe ich aber noch nicht gelöst bekommen

    1. Die Methode bzw. das Beispiel von 31415... (PI) mit dem std::bind hat wunderbar funktioniert. Wie mache es aber, wenn die Funktion (im Beispiel sayHello) Argumente hat. Also z.B.

    class MyClass
    {
        void sayHello(char *str) { std::cout << str << std::endl; }
    };
    

    Eine Kurze Suche zu dieser Methode hat ergeben, dass man Platzhalter einführen muss. Wenn ich es richtig verstanden haben, dann müsste es so aussehen

    int main()
    {
        MyClass obj;
        std::function<void()> f = std::bind(&MyClass::sayHello, &obj, _1);
        f("Hallo");
    }
    

    Allerdings kann Visual Studio mit dem Platzhalter _1 bei mir nichts anfangen.

    2. Lambda-Funktion. Bei JavaScript bin ich so etwas gewohnt. Wusste gar nicht, dass C++ das nun auch beherrscht. Der Compiler will aber nicht so richtig. Hier der (fehlerhafte) Code/Zeile:

    rk4(4,nx,ny,t,dt,[&K](double t,double *x,double *y) {K.ODE(t,x,y);});
    

    und hier die Fehlermeldung:

    error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""void __cdecl rk4<class `anonymous namespace'::<lambda0> >(int,double *,double *,double,double,class `anonymous namespace'::<lambda0>)" (??$rk4@V<lambda0>@?A0x2df2e2fb@@@@YAXHPAN0NNV<lambda0>@?A0x2df2e2fb@@@Z)" in Funktion "_main".

    Bei der Funktor-Geschichte bekomme ich den gleichen Fehler-Code (ähnlicher Text). Hier der Code:

    class Funktor
    {
    private:
    	Kreisel& K;
    public:
    
    	explicit Funktor(Kreisel& _K)
    		:K(_K)
    	{}
    
    	void operator()(double t, double *x, double *y)
    	{
    		K.ODE(t,x,y);
    	}
    
    // in main()
    rk4(4,nx,ny,t,dt,Funktor(K));
    };
    


  • Du musst dem function-Object natürlich auch sagen, dass es eine Funktion enthält, die einen char* nimmt 😉

    int main()
    {
        MyClass obj;
        std::function<void(char*)> f = std::bind(&MyClass::sayHello, &obj, _1);
        f("Hallo");
    }
    


  • Ich habe noch einmal nachgesehen, folgendermaßen funktioniert es:

    #include <functional>
    #include <iostream>
    
    struct MyClass
    {
    	void say_something(const char* str) { std::cout << str << std::endl; }
    };
    
    int main()
    {
    	MyClass obj;
    	std::function<void(const char*)> f = std::bind(&MyClass::say_something, &obj, std::placeholders::_1);
    	f("Hello World");
    }
    

    In boost reicht es, wenn man nur _1 schreibt, bei VC++ muss man std::placeholders::_1 schreiben.



  • Nur, dass das klar ist: std::bind, std::placeholders::_1, std::function, Lambdas gehören nur zum kommenden C++ Standard. Im aktuellen C++ Standard gibt es diese Dinger nicht. In TR1 gibt es bind und function, dort aber im Namensraum std::tr1, wenn ich mich richtig erinnere.

    Die Linker-Fehlermeldung ist komisch. Ohne weiteren Kontext, kann ich damit nichts anfangen.

    Ich schätze, man hat die Placeholder deswegen in einen eigenen Namensraum gepackt, damit man sich diesen per using-Direktive "reinholen" kann, also

    using namespate std::placeholders;
    std::bind(dies,das,_1,_3,_2);
    

    Ohne Boost, TR1, C++0x kann man immer noch die beiden Ansätze fahren, die ich vorgeschlagen hatte (wenn man das Lambda-Objekt durch einen eigenen Funktor ersetzt).



  • Natürlich ist das C++0x, aber wenn VC++ 10 das unterstützt, kann man es doch nutzen 😉



  • Die Placeholder der Boost stecken IIRC auch in ihrem eigenen Namespace, bloss werden sie irgendwo in den Boost Headern schon per using reingeholt.

    Wobei das IMO eh sehr fragwürdig ist.



  • Vielen Dank für eure schnellen und hilfreichen Antworten!

    Habe jetzt alle genannten "Wege nach Rom" ausprobiert und bin auch (fast) in Rom angekommen 🙂

    Die Alternative mit der Lambda-Funktion hat mir am besten gefallen. Vor allem weil ich deren Verwendung bei JavaScript gewohnt bin. Für die, die es genau wissen wollen, ich habe es so gemacht:

    rk4(...,[this](double t,double *x,double *y) {ODE(t,x,y);});
    

    Wobei ich die Runge-Kutta-Methode rk4 ins cpp-file der Klasse kopiert habe und die Methode dann auch in einer Methode der Klasse aufrufen muss. Sonst kommt es bei mir zu dem letztens genannten Link-Fehler. Blicke bei dieser Link-Geschichte nämlich noch nicht so ganz durch. Schön wäre aber eine getrennte Lösung. Wie müsste ich denn die Klasse und die RK-Methode Verteilen/Verlinken damit ich sie dann auch im main() aufrufen kann? Wenn ich es so machen:

    #include "classKoerper.h" // der physikalische Körper mit der charakteristischen DGL (engl.: ODE)
    #include "RungeKutta.h" // die allgemeine RK-Methode (benötigt ODE-Methode)
    
    // später in der main()
    Koerper K(...);
    rk4(...,[&K](double t,double *x,double *y) {K.ODE(t,x,y);});
    

    dann funktioniert es nicht -> LINK-Fehler 😞

    Hier habe ich ansonsten noch eine kleine nette Zusammenfassung über die Lambda-Funktion mit einer Erklärung zur Syntax gefunden:
    http://en.wikipedia.org/wiki/C%2B%2B0x#Lambda_functions_and_expressions



  • Du müsstest Dich intensiver mit folgenden Dingen beschäftigen:
    1. Konzept der getrennten Übersetzung
    2. Was sind Templates?
    3. Was ist das "Inclusion Model" bzgl Templates?
    4. ODR (one definition rule)
    5. Was liefert ein Lambda-Ausdruck?

    Du bist in eine "Falle" gelaufen, in die viele reinlaufen, wenn sie versuchen Templates zu benutzen, ohne die Konzepte dahinter zu verstehen.

    Entweder Du packst die Implementierung der RungeKutta-Funktion in einen Header

    #ifndef RK4_HPP_INC
    #define RK4_HPP_INC
    
    template<class Func>
    void rk4(???,Func f)
    {
      ???
    }
    
    #endif//RK4_HPP_INC
    

    oder Du machst aus rk4 eine normale Funktion, deklarierst sie nur im Header und definierst sie in einer cpp-Datei. Du kannst hier ausnutzen, dass passende Funktor-Typen in std::function<???> konvertierbar sind.

    #ifndef RK4_HPP_INC
    #define RK4_HPP_INC
    
    #include <functional>
    
    void rk4(???,std::function<void(int,double,double*)> f);
    
    #endif//RK4_HPP_INC
    
    #include "rk4.hpp"
    
    void rk4(???,std::function<void(int,double,double*)> f)
    {
      ...
    }
    

    Letzteres wird aber etwas langsamer aufgrund des zusätzlichen "Level of indirection". Aufrufen kannst Du beide Funktionen genau gleich.

    Warum wieso weshalb musst Du selbst nachlesen. Schnapp Dir ein schlaues C++ Buch und lerne.


Anmelden zum Antworten