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


170

Pour une classe, je souhaite stocker des pointeurs de fonction vers des fonctions membres de la même classe dans un objet de mapstockage std::function. Mais j'échoue dès le début 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 argumentsen xxcallobjcombinaison avec des erreurs d'instanciation de modèle étranges. Actuellement, je travaille sur Windows 8 avec Visual Studio 2010/2011 et sur Win 7 avec VS10, cela échoue également. L'erreur doit être basée sur des règles C ++ étranges que je ne suis pas

Réponses:


301

Une fonction membre non statique doit être appelée avec un objet. Autrement dit, il passe toujours implicitement "ce" pointeur comme argument.

Étant donné que votre std::functionsignature spécifie que votre fonction ne prend aucun argument ( <void(void)>), vous devez lier 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, std::placeholders::_1, std::placeholders::_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 compatible C ++ 11 à portée de main pour le moment , donc je ne peux pas vérifier celui-ci.)


1
Comme je ne suis pas dépendant de boost, j'utiliserai des expressions lambda;) Néanmoins merci!
Christian Ivicevic

3
@AlexB: Boost.Bind n'utilise pas ADL pour les espaces réservés, il les place dans un espace de noms anonyme.
ildjarn

46
Je recommande d'éviter la capture globale [=] et d'utiliser [this] pour clarifier ce qui est capturé (Scott Meyers - Effective Modern C ++ Chapter 6. item 31 - Eviter les modes de capture par défaut)
Max Raskin

5
Ajoutez simplement un petit conseil: le pointeur de la fonction membre peut être implicitement converti en std::function, avec un supplément thiscomme premier paramètre, commestd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung

@landerlyoung: Ajoutez le nom de la fonction par exemple "f" ci-dessus pour corriger l'exemple de syntaxe. Si vous n'avez pas besoin du nom, vous pouvez utiliser mem_fn (& Foo :: doSomethingArgs).
Val

80

Soit vous avez besoin

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

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

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

Merci pour cette excellente réponse: D Exactement ce dont j'ai besoin, je n'ai pas pu trouver comment spécialiser une fonction std :: pour appeler une fonction membre sur une instance de classe.
penelope

Cela compile, mais est-ce standard? Etes - vous assuré que le premier argument est this?
sudo rm -rf slash

@ sudorm-rfslash oui, vous êtes
Armen Tsirunyan

Merci pour la réponse @ArmenTsirunyan ... où dans la norme puis-je rechercher cette information?
sudo rm -rf slash

13

Si vous avez besoin de stocker une fonction membre sans l'instance de 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 transmettre ce stockage de fonction à 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 ancienne interface std :: mem_func existait, mais est depuis obsolète. Une proposition existe, post C ++ 17, pour rendre le pointeur vers les fonctions membres appelables . Ce serait le bienvenu.


@Danh std::mem_fnn'a pas été supprimé; un tas de surcharges inutiles étaient. D'autre part, il std::mem_funest obsolète avec C ++ 11 et sera supprimé avec C ++ 17.
Max Truxa

@Danh C'est exactement ce dont je parle;) La toute première surcharge "basique" est toujours là:, template<class R, class T> unspecified mem_fn(R T::*);et elle ne disparaîtra pas.
Max Truxa

@Danh Lisez attentivement le DR . 12 surcharges sur 13 ont été supprimées par le DR. Ce dernier n'était pas (et ne le sera pas; ni en C ++ 11 ni en C ++ 14).
Max Truxa

1
Pourquoi voter contre? Toutes les autres réponses indiquent que vous devez lier l'instance de classe. Si vous créez un système de liaison pour la réflexion ou la création de scripts, vous ne voudrez pas le faire. Cette méthode alternative est valable et pertinente pour certaines personnes.
Greg

Merci Danh, j'ai édité avec quelques commentaires sur les interfaces passées et futures liées.
Greg

3

Malheureusement, C ++ ne vous permet pas d'obtenir directement un objet appelable faisant référence à un objet et à l'une de ses fonctions membres. &Foo::doSomethingvous donne un "pointeur vers la fonction membre" qui fait référence à la fonction membre mais pas à l'objet associé.

Il existe deux façons de contourner ce problème, l'une consiste à std::bindlier le «pointeur vers la fonction membre» au thispointeur. L'autre consiste à utiliser un lambda qui capture le thispointeur et appelle la fonction membre.

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
std::function<void(void)> g = [this](){doSomething();};

Je préférerais ce dernier.

Avec g ++ au moins lier une fonction membre à ceci se traduira par un objet à trois pointeurs de taille, l'assigner à un std::functionentraînera une allocation de mémoire dynamique.

D'un autre côté, un lambda qui capture thisn'a qu'un seul pointeur en taille, l'assigner à un std::functionne résultera pas en une allocation de mémoire dynamique avec g ++.

Bien que je ne l'ai pas vérifié avec d'autres compilateurs, je soupçonne que des résultats similaires y seront trouvés.


1

Vous pouvez utiliser des foncteurs si vous souhaitez 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 vers 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;
};

Auditeur.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

Vous pouvez éviter de std::bindfaire ceci:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.