web-dev-qa-db-fra.com

Utilisation d'objets std :: function génériques avec des fonctions membres dans une classe

Pour une classe, je souhaite stocker des pointeurs de fonction vers des fonctions membres de la même classe dans un seul objet map, stockant std::function. Mais je manque tout de suite avec ce code:

class Foo {
    public:
        void doSomething() {}
        void bindFunction() {
            // ERROR
            std::function<void(void)> f = &Foo::doSomething;
        }
};

Je reçois error C2064: term does not evaluate to a function taking 0 arguments dans xxcallobj combiné à des erreurs d’instanciation de modèles étranges. Actuellement, je travaille sur Windows 8 avec Visual Studio 2010/2011 et sur Windows 7 avec VS10, il échoue également. L'erreur doit être basée sur des règles C++ étranges que je ne suis pas.

EDIT: Je fais PAS utiliser boost. C++ 11 est intégré au compilateur MS.

138
Christian Ivicevic

Une fonction membre non statique doit être appelée avec un objet. C'est-à-dire qu'il passe toujours implicitement "ce" pointeur comme argument.

Parce que votre signature std::function indique que votre fonction ne prend aucun argument (<void(void)>), vous devez bind le premier (et le seul) argument.

std::function<void(void)> f = std::bind(&Foo::doSomething, this);

Si vous souhaitez lier une fonction avec des paramètres, vous devez spécifier des espaces réservés:

using namespace std::placeholders;
std::function<void(int,int)> f = std::bind(&Foo::doSomethingArgs, this, _1, _2);

Ou, si votre compilateur prend en charge les lambdas C++ 11:

std::function<void(int,int)> f = [=](int a, int b) {
    this->doSomethingArgs(a, b);
}

(Je n'ai pas de compilateur capable de gérer C++ 11 sous la main maintenant, je ne peux donc pas vérifier celui-ci.)

251
Alex B

Soit vous avez besoin

std::function<void(Foo*)> f = &Foo::doSomething;

afin que vous puissiez l'appeler sur n'importe quelle instance ou que vous deviez lier une instance spécifique, par exemple this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
65
Armen Tsirunyan

Si vous avez besoin de stocker une fonction membre sans l'instance de la classe, vous pouvez faire quelque chose comme ceci:

class MyClass
{
public:
    void MemberFunc(int value)
    {
      //do something
    }
};

// Store member function binding
auto callable = std::mem_fn(&MyClass::MemberFunc);

// Call with late supplied 'this'
MyClass myInst;
callable(&myInst, 123);

À quoi ressemblerait le type de stockage sans auto ? Quelque chose comme ça:

std::_Mem_fn_wrap<void,void (__cdecl TestA::*)(int),TestA,int> callable

Vous pouvez également passer cette fonction de stockage à une liaison de fonction standard

std::function<void(int)> binding = std::bind(callable, &testA, std::placeholders::_1);
binding(123); // Call

Notes passées et futures: Une interface plus ancienne std :: mem_func existait, mais est depuis déconseillée. Une proposition existe, post C++ 17, pour rendre pointeur sur les fonctions membres appelable . Ce serait le bienvenu.

11
Greg

Vous pouvez utiliser des foncteurs si vous voulez un contrôle moins générique et plus précis sous le capot. Exemple avec mon API win32 pour transférer un message api d'une classe à une autre classe.

IListener.h

#include <windows.h>
class IListener { 
    public:
    virtual ~IListener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
};

Listener.h

#include "IListener.h"
template <typename D> class Listener : public IListener {
    public:
    typedef LRESULT (D::*WMFuncPtr)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 

    private:
    D* _instance;
    WMFuncPtr _wmFuncPtr; 

    public:
    virtual ~Listener() {}
    virtual LRESULT operator()(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) override {
        return (_instance->*_wmFuncPtr)(hWnd, uMsg, wParam, lParam);
    }

    Listener(D* instance, WMFuncPtr wmFuncPtr) {
        _instance = instance;
        _wmFuncPtr = wmFuncPtr;
    }
};

Dispatcher.h

#include <map>
#include "Listener.h"

class Dispatcher {
    private:
        //Storage map for message/pointers
        std::map<UINT /*WM_MESSAGE*/, IListener*> _listeners; 

    public:
        virtual ~Dispatcher() { //clear the map }

        //Return a previously registered callable funtion pointer for uMsg.
        IListener* get(UINT uMsg) {
            typename std::map<UINT, IListener*>::iterator itEvt;
            if((itEvt = _listeners.find(uMsg)) == _listeners.end()) {
                return NULL;
            }
            return itEvt->second;
        }

        //Set a member function to receive message. 
        //Example Button->add<MyClass>(WM_COMMAND, this, &MyClass::myfunc);
        template <typename D> void add(UINT uMsg, D* instance, typename Listener<D>::WMFuncPtr wmFuncPtr) {
            _listeners[uMsg] = new Listener<D>(instance, wmFuncPtr);
        }

};

Principes d'utilisation

class Button {
    public:
    Dispatcher _dispatcher;
    //button window forward all received message to a listener
    LRESULT onMessage(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        //to return a precise message like WM_CREATE, you have just
        //search it in the map.
        return _dispatcher[uMsg](hWnd, uMsg, w, l);
    }
};

class Myclass {
    Button _button;
    //the listener for Button messages
    LRESULT button_listener(HWND hWnd, UINT uMsg, WPARAM w, LPARAM l) {
        return 0;
    }

    //Register the listener for Button messages
    void initialize() {
        //now all message received from button are forwarded to button_listener function 
       _button._dispatcher.add(WM_CREATE, this, &Myclass::button_listener);
    }
};

Bonne chance et merci à tous pour le partage des connaissances.

0
user11158633