Używanie ogólnych obiektów std :: function z funkcjami składowymi w jednej klasie

169

Dla jednej klasy chcę przechowywać niektóre wskaźniki funkcji do funkcji składowych tej samej klasy w jednej mapprzechowującej std::functionobiekty. Ale zawodzę na samym początku z tym kodem:

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

Otrzymuję error C2064: term does not evaluate to a function taking 0 argumentsw xxcallobjpołączeniu z dziwnymi błędami podczas tworzenia szablonu. Obecnie pracuję na Windows 8 z Visual Studio 2010/2011 i na Win 7 z VS10 też się nie udaje. Błąd musi wynikać z jakichś dziwnych reguł C ++, których nie przestrzegam

Christian Ivicevic
źródło

Odpowiedzi:

301

Niestatyczną funkcję składową należy wywołać z obiektem. Oznacza to, że jako argument zawsze pośrednio przekazuje wskaźnik „ten”.

Ponieważ twój std::functionpodpis określa, że ​​twoja funkcja nie przyjmuje żadnych argumentów ( <void(void)>), musisz powiązać pierwszy (i jedyny) argument.

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

Jeśli chcesz powiązać funkcję z parametrami, musisz określić symbole zastępcze:

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

Lub, jeśli Twój kompilator obsługuje lambdy języka C ++ 11:

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

(I nie masz c ++ 11 zdolną kompilatora pod ręką w tej chwili , więc nie mogę sprawdzić ten jeden).

Alex B.
źródło
1
Ponieważ nie jestem uzależniony od boostu, użyję wyrażeń lambda;) Mimo wszystko dzięki!
Christian Ivicevic
3
@AlexB: Boost.Bind nie używa ADL dla symboli zastępczych, umieszcza je w anonimowej przestrzeni nazw.
ildjarn
46
Zalecam unikanie globalnego przechwytywania [=] i używanie [this], aby wyraźniej określić, co jest przechwytywane (Scott Meyers - Effective Modern C ++ Rozdział 6. pozycja 31 - Unikaj domyślnych trybów przechwytywania)
Max Raskin
5
Po prostu dodaj małą wskazówkę: wskaźnik funkcji składowej może być niejawnie rzutowany na std::function, z dodatkowym thisjako pierwszym parametrem, na przykładstd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung
@landerlyoung: Dodaj nazwę funkcji, powiedz „f” powyżej, aby poprawić przykładową składnię. Jeśli nie potrzebujesz nazwy, możesz użyć mem_fn (& Foo :: doSomethingArgs).
Val
80

Albo potrzebujesz

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

abyś mógł go wywołać w dowolnej instancji lub na przykład musisz powiązać konkretną instancję this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
Armen Tsirunyan
źródło
Dziękuję za tę świetną odpowiedź: D Dokładnie to, czego potrzebuję, nie mogłem znaleźć specjalizacji funkcji std :: do wywoływania funkcji składowej w dowolnej instancji klasy.
penelope
To się kompiluje, ale czy jest to standardowe? Czy masz gwarancję, że pierwszy argument jest this?
sudo rm -rf slash,
@ sudorm-rfslash tak, jesteś
Armen Tsirunyan
Dzięki za odpowiedź @ArmenTsirunyan ... gdzie w standardzie mogę szukać tych informacji?
sudo rm -rf slash
13

Jeśli chcesz zapisać funkcję składową bez instancji klasy, możesz zrobić coś takiego:

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

Jak wyglądałby typ pamięci bez auto ? Coś takiego:

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

Możesz również przekazać tę pamięć funkcji do standardowego powiązania funkcji

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

Poprzednie i przyszłe uwagi: istniał starszy interfejs std :: mem_func , ale od tamtej pory został uznany za przestarzały. Istnieje propozycja po C ++ 17, aby umożliwić wywoływanie wskaźnika do funkcji składowych . Byłoby to bardzo mile widziane.

Greg
źródło
@Danh niestd::mem_fn został usunięty; było kilka niepotrzebnych przeciążeń. Z drugiej strony został uznany za przestarzały w C ++ 11 i zostanie usunięty z C ++ 17. std::mem_fun
Max Truxa
@Danh Dokładnie o tym mówię;) Pierwsze "podstawowe" przeciążenie wciąż istnieje: template<class R, class T> unspecified mem_fn(R T::*);i nie zniknie.
Max Truxa
@Danh Przeczytaj uważnie DR . 12 z 13 przeciążeń zostało usuniętych przez DR. Ten ostatni nie był (i nie będzie; ani w C ++ 11 ani C ++ 14).
Max Truxa
1
Dlaczego głosowanie negatywne? Każda inna odpowiedź mówiła, że ​​musisz powiązać instancję klasy. Jeśli tworzysz wiążący system do refleksji lub skryptów, nie będziesz chciał tego robić. Ta alternatywna metoda jest ważna i odpowiednia dla niektórych osób.
Greg
Dzięki Danh, zredagowałem, dodając kilka komentarzy na temat powiązanych przeszłych i przyszłych interfejsów.
Greg,
3

Niestety, C ++ nie pozwala na bezpośrednie uzyskanie wywoływalnego obiektu odnoszącego się do obiektu i jednej z jego funkcji składowych. &Foo::doSomethingdaje „wskaźnik do funkcji składowej”, który odnosi się do funkcji składowej, ale nie do skojarzonego obiektu.

Istnieją dwa sposoby obejścia tego problemu, jednym z nich jest std::bindpowiązanie „wskaźnika do funkcji składowej” ze thiswskaźnikiem. Drugim jest użycie lambdy, która przechwytuje thiswskaźnik i wywołuje funkcję składową.

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

Wolałbym to drugie.

Przy g ++ co najmniej powiązanie funkcji składowej z tym spowoduje, że obiekt będzie miał rozmiar trzech wskaźników, przypisanie tego do obiektu std::functionspowoduje dynamiczną alokację pamięci.

Z drugiej strony przechwytująca lambda thisma rozmiar tylko jednego wskaźnika, przypisanie jej do std::functionzmiennej nie spowoduje dynamicznej alokacji pamięci za pomocą g ++.

Chociaż nie zweryfikowałem tego z innymi kompilatorami, podejrzewam, że zostaną tam znalezione podobne wyniki.

plugwash
źródło
1

Możesz użyć funktorów, jeśli chcesz mieć mniej ogólne i bardziej precyzyjne sterowanie pod maską. Przykład z moim api win32, aby przesłać wiadomość api z klasy do innej klasy.

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

};

Zasady użytkowania

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

Powodzenia i dziękuję wszystkim za podzielenie się wiedzą.


źródło
0

Możesz tego uniknąć std::bind:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
aggsol
źródło