Verwenden generischer std :: function-Objekte mit Elementfunktionen in einer Klasse

169

Für eine Klasse möchte ich einige Funktionszeiger auf Mitgliedsfunktionen derselben Klasse in einem Objekt mapspeichern std::function. Aber ich versage gleich zu Beginn mit diesem Code:

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

Ich erhalte error C2064: term does not evaluate to a function taking 0 argumentsin xxcallobjKombination mit einigen seltsamen Vorlageninstanziierungsfehlern. Derzeit arbeite ich unter Windows 8 mit Visual Studio 2010/2011 und unter Win 7 mit VS10 schlägt dies ebenfalls fehl. Der Fehler muss auf einigen seltsamen C ++ - Regeln basieren, denen ich nicht folge

Christian Ivicevic
quelle

Antworten:

300

Eine nicht statische Elementfunktion muss mit einem Objekt aufgerufen werden. Das heißt, es wird immer implizit "dieser" Zeiger als Argument übergeben.

Da Ihre std::functionSignatur angibt, dass Ihre Funktion keine Argumente akzeptiert ( <void(void)>), müssen Sie das erste (und das einzige) Argument binden .

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

Wenn Sie eine Funktion mit Parametern verknüpfen möchten, müssen Sie Platzhalter angeben:

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

Oder wenn Ihr Compiler C ++ 11 Lambdas unterstützt:

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

(Ich habe keine C ++ 11 fähig Compiler zur Hand gerade jetzt , so dass ich nicht dies überprüfen.)

Alex B.
quelle
1
Da ich nicht auf Boost angewiesen bin, werde ich Lambda-Ausdrücke verwenden;) Trotzdem danke!
Christian Ivicevic
3
@AlexB: Boost.Bind verwendet ADL nicht für die Platzhalter, sondern platziert sie in einem anonymen Namespace.
ildjarn
46
Ich empfehle, die globale Erfassung [=] zu vermeiden und [dies] zu verwenden, um klarer zu machen, was erfasst wird (Scott Meyers - Effektives modernes C ++ Kapitel 6. Punkt 31 - Vermeiden Sie Standarderfassungsmodi)
Max Raskin
5
std::functionthisstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
Fügen
@landerlyoung: Fügen Sie den Namen der Funktion hinzu, beispielsweise "f", um die Beispielsyntax zu korrigieren. Wenn Sie den Namen nicht benötigen, können Sie mem_fn (& Foo :: doSomethingArgs) verwenden.
Val
80

Entweder du brauchst

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

Damit Sie es auf einer beliebigen Instanz aufrufen können oder beispielsweise eine bestimmte Instanz binden müssen this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
Armen Tsirunyan
quelle
Vielen Dank für diese großartige Antwort: D Genau das, was ich brauche, konnte ich nicht finden, wie ich eine std :: -Funktion spezialisieren kann, um eine Mitgliedsfunktion für eine Klasseninstanz aufzurufen.
Penelope
Dies wird kompiliert, aber ist es Standard? Ist Ihnen das erste Argument garantiert this?
Sudo RM-RF Slash
@ Sudorm-Rfslash Ja, Sie sind
Armen Tsirunyan
Vielen Dank für die Antwort @ArmenTsirunyan ... wo im Standard kann ich nach diesen Informationen suchen?
Sudo RM-RF Slash
13

Wenn Sie eine Member-Funktion ohne die Klasseninstanz speichern müssen , können Sie Folgendes tun:

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

Wie würde der Speichertyp ohne Auto aussehen ? Etwas wie das:

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

Sie können diesen Funktionsspeicher auch an eine Standardfunktionsbindung übergeben

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

Frühere und zukünftige Hinweise: Eine ältere Schnittstelle std :: mem_func war vorhanden, wurde jedoch inzwischen nicht mehr unterstützt. Nach C ++ 17 gibt es einen Vorschlag, den Zeiger auf Mitgliedsfunktionen aufrufbar zu machen . Dies wäre sehr willkommen.

Greg
quelle
@ Danh std::mem_fnwurde nicht entfernt; eine Menge unnötiger Überlastungen waren. Andererseits std::mem_funwurde mit C ++ 11 veraltet und wird mit C ++ 17 entfernt.
Max Truxa
@Danh Genau darüber spreche ich;) Die allererste "grundlegende" Überlastung ist immer noch da: template<class R, class T> unspecified mem_fn(R T::*);und sie wird nicht verschwinden.
Max Truxa
@ Danh Lesen Sie die DR sorgfältig. 12 von 13 Überladungen wurden von der DR entfernt. Letzteres war nicht (und wird es auch nicht sein; weder in C ++ 11 noch in C ++ 14).
Max Truxa
1
Warum die Abwahl? Bei jeder zweiten Antwort wurde angegeben, dass Sie die Klasseninstanz binden müssen. Wenn Sie ein Bindungssystem für Reflexion oder Skripterstellung erstellen, möchten Sie dies nicht tun. Diese alternative Methode ist für einige Personen gültig und relevant.
Greg
Danke Danh, ich habe einige Kommentare zu verwandten vergangenen und zukünftigen Schnittstellen bearbeitet.
Greg
3

Leider können Sie in C ++ kein aufrufbares Objekt direkt abrufen, das auf ein Objekt und eine seiner Mitgliedsfunktionen verweist. &Foo::doSomethinggibt Ihnen einen "Zeiger auf die Elementfunktion", der sich auf die Elementfunktion bezieht, jedoch nicht auf das zugehörige Objekt.

Es gibt zwei Möglichkeiten, dies std::bindzu umgehen. Eine besteht darin , den "Zeiger auf die thisElementfunktion " an den Zeiger zu binden . Die andere Möglichkeit besteht darin, ein Lambda zu verwenden, das den thisZeiger erfasst und die Elementfunktion aufruft.

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

Letzteres würde ich vorziehen.

Wenn g ++ mindestens eine Mitgliedsfunktion daran bindet, führt dies zu einem Objekt mit einer Größe von drei Zeigern. Wenn Sie dies einem Objekt zuweisen, wird ein std::functiondynamischer Speicher zugewiesen.

Auf der anderen Seite ist ein Lambda, das erfasst, thisnur ein Zeiger groß. Wenn Sie es einem Lambda zuweisen, std::functionführt dies nicht zu einer dynamischen Speicherzuweisung mit g ++.

Obwohl ich dies mit anderen Compilern nicht überprüft habe, vermute ich, dass dort ähnliche Ergebnisse gefunden werden.

Plugwash
quelle
1

Sie können Funktoren verwenden, wenn Sie eine weniger allgemeine und präzisere Steuerung unter der Haube wünschen. Beispiel mit meiner win32-API zum Weiterleiten einer API-Nachricht von einer Klasse an eine andere Klasse.

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

};

Verwendungsprinzipien

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

Viel Glück und vielen Dank für den Wissensaustausch.


quelle
0

Sie können dies vermeiden std::bind:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
aggsol
quelle