Utilizzo di oggetti generici std :: function con funzioni membro in una classe


170

Per una classe voglio memorizzare alcuni puntatori di funzione alle funzioni membro della stessa classe in un oggetto di mapmemorizzazione std::function. Ma non riesco proprio all'inizio con questo codice:

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

Ricevo error C2064: term does not evaluate to a function taking 0 argumentsin xxcallobjcombinazione con alcuni strani errori di istanza del modello. Attualmente sto lavorando su Windows 8 con Visual Studio 2010/2011 e anche su Win 7 con VS10 non funziona. L'errore deve essere basato su alcune strane regole C ++ che non seguo

Risposte:


301

Una funzione membro non statica deve essere chiamata con un oggetto. Cioè, passa sempre implicitamente "questo" puntatore come argomento.

Poiché la tua std::functionfirma specifica che la tua funzione non accetta alcun argomento ( <void(void)>), devi associare il primo (e unico) argomento.

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

Se si desidera associare una funzione con parametri, è necessario specificare segnaposto:

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

Oppure, se il tuo compilatore supporta C ++ 11 lambdas:

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

(Non ho un 11 in grado compilatore a portata di mano C ++ in questo momento , quindi non posso controllare questo.)


1
Dato che non dipendo da boost userò espressioni lambda;) Tuttavia grazie!
Christian Ivicevic,

3
@AlexB: Boost.Bind non utilizza ADL per i segnaposto, li inserisce in uno spazio dei nomi anonimo.
ildjarn,

46
Consiglio di evitare la cattura globale [=] e di usare [this] per rendere più chiaro ciò che viene catturato (Scott Meyers - Effective Modern C ++ Chapter 6. item 31 - Evita le modalità di cattura predefinite)
Max Raskin,

5
Basta aggiungere un piccolo suggerimento: il puntatore della funzione membro può essere implicitamente lanciato std::function, con extra thiscome primo parametro, comestd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung

@landerlyoung: aggiungi il nome della funzione pronuncia come "f" sopra per correggere la sintassi di esempio. Se non ti serve il nome, puoi usare mem_fn (& Foo :: doSomethingArgs).
Val

80

O hai bisogno

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

in modo da poterlo chiamare in qualsiasi istanza o è necessario associare un'istanza specifica, ad esempio this

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

Grazie per questa grande risposta: D Esattamente quello di cui ho bisogno, non sono riuscito a trovare come specializzare una funzione std :: per chiamare una funzione membro su qualsiasi istanza di classe.
penelope,

Questo compila, ma è standard? Sei sicuro che il primo argomento sia this?
sudo rm -rf slash

@ sudorm-rfslash sì, lo sei
Armen Tsirunyan,

Grazie per la risposta @ArmenTsirunyan ... dove nello standard posso cercare queste informazioni?
sudo rm -rf slash

13

Se è necessario archiviare una funzione membro senza l'istanza della classe, è possibile fare qualcosa del genere:

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

Come sarebbe il tipo di archiviazione senza auto ? Qualcosa come questo:

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

È inoltre possibile passare questa memoria funzioni a un binding di funzioni standard

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

Note passate e future: esisteva un'interfaccia più vecchia std :: mem_func , ma da allora è diventata obsoleta. Esiste una proposta, post C ++ 17, per rendere richiamabile il puntatore alle funzioni membro . Questo sarebbe il benvenuto.


@Danh nonstd::mem_fn è stato rimosso; c'erano un sacco di sovraccarichi inutili. D'altra parte è std::mem_funstato deprecato con C ++ 11 e verrà rimosso con C ++ 17.
Max Truxa,

@Danh Questo è esattamente ciò di cui sto parlando;) Il primissimo sovraccarico "di base" è ancora lì: template<class R, class T> unspecified mem_fn(R T::*);e non sparirà.
Max Truxa,

@Danh Leggi attentamente il DR . 12 sovraccarichi su 13 sono stati rimossi dal DR. Quest'ultimo non lo era (e non lo sarà; né in C ++ 11 o C ++ 14).
Max Truxa,

1
Perché il voto negativo? Ogni altra risposta diceva che devi associare l'istanza di classe. Se stai creando un sistema vincolante per la riflessione o lo scripting, non vorrai farlo. Questo metodo alternativo è valido e pertinente per alcune persone.
Greg,

Grazie Danh, ho modificato alcuni commenti sulle relative interfacce passate e future.
Greg,

3

Sfortunatamente, C ++ non consente di ottenere direttamente un oggetto richiamabile che fa riferimento a un oggetto e una delle sue funzioni membro. &Foo::doSomethingti dà un "puntatore alla funzione membro" che si riferisce alla funzione membro ma non all'oggetto associato.

Esistono due modi per aggirare questo problema, uno è quello std::binddi associare il "puntatore alla funzione membro" al thispuntatore. L'altro è usare un lambda che cattura il thispuntatore e chiama la funzione membro.

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

Preferirei quest'ultimo.

Con g ++ almeno un vincolo di una funzione membro a questo si tradurrà in un oggetto di dimensioni di tre puntatori, l'assegnazione a un std::functioncomporterà l'allocazione dinamica della memoria.

D'altra parte, un lambda che acquisisce ha thissolo un puntatore di dimensioni, assegnandolo a un std::functionnon comporterà l'allocazione dinamica della memoria con g ++.

Anche se non ho verificato questo con altri compilatori, sospetto che risultati simili possano essere trovati lì.


1

È possibile utilizzare i funzioni se si desidera un controllo meno generico e più preciso sotto il cofano. Esempio con il mio Win32 API per inoltrare il messaggio API da una classe a un'altra 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);
        }

};

Principi d'uso

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

Buona fortuna e grazie a tutti per aver condiviso le conoscenze.


0

Puoi evitare di std::bindfarlo:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.