Uso de objetos genéricos std :: function con funciones miembro en una clase


170

Para una clase, quiero almacenar algunos punteros de función para funciones miembro de la misma clase en un objeto de mapalmacenamiento std::function. Pero fallo al principio con este código:

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

Recibo error C2064: term does not evaluate to a function taking 0 argumentsen xxcallobjcombinación con algunos errores de instanciación de plantilla extraños. Actualmente estoy trabajando en Windows 8 con Visual Studio 2010/2011 y en Win 7 con VS10 también falla. El error debe basarse en algunas reglas extrañas de C ++ que no sigo

Respuestas:


301

Una función miembro no estática debe llamarse con un objeto. Es decir, siempre pasa implícitamente "este" puntero como argumento.

Debido a que su std::functionfirma especifica que su función no toma ningún argumento ( <void(void)>), debe vincular el primer (y único) argumento.

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

Si desea vincular una función con parámetros, debe especificar marcadores de posición:

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

O, si su compilador admite C ++ 11 lambdas:

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

(No tengo un compilador compatible con C ++ 11 en este momento , por lo que no puedo comprobarlo).


1
Como no soy dependiente del impulso, usaré expresiones lambda;) Sin embargo, ¡gracias!
Christian Ivicevic

3
@AlexB: Boost.Bind no usa ADL para los marcadores de posición, los coloca en un espacio de nombres anónimo.
ildjarn

46
Recomiendo evitar la captura global [=] y usar [esto] para aclarar lo que se captura (Scott Meyers - Eficaz Modern C ++ Capítulo 6. elemento 31 - Evitar modos de captura predeterminados)
Max Raskin

55
Solo agregue un pequeño consejo: el puntero de la función miembro se puede convertir implícitamente std::function, con extra thiscomo primer parámetro, comostd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung el

@landerlyoung: Agregue el nombre de la función, como "f", arriba para corregir la sintaxis de muestra. Si no necesita el nombre, puede usar mem_fn (& Foo :: doSomethingArgs).
Val

80

O necesitas

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

para que pueda llamarlo en cualquier instancia, o necesita vincular una instancia específica, por ejemplo this

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

Gracias por esta gran respuesta: D Exactamente lo que necesito, no pude encontrar cómo especializar una función std :: para llamar a una función miembro en cualquier instancia de clase.
Penélope

Esto compila, pero ¿es estándar? ¿Estás seguro de que el primer argumento es this?
sudo rm -rf slash

@ sudorm-rfslash sí, lo eres
Armen Tsirunyan

Gracias por la respuesta @ArmenTsirunyan ... ¿en qué parte del estándar puedo buscar esta información?
sudo rm -rf slash

13

Si necesita almacenar una función miembro sin la instancia de clase, puede hacer algo como esto:

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

¿Cómo sería el tipo de almacenamiento sin auto ? Algo como esto:

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

También puede pasar este almacenamiento de funciones a un enlace de función estándar

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

Notas pasadas y futuras: existía una interfaz anterior std :: mem_func , pero desde entonces ha quedado en desuso. Existe una propuesta, posterior a C ++ 17, para hacer que el puntero a las funciones miembro sea invocable . Esto sería muy bienvenido.


@Danh nostd::mem_fn fue eliminado; un montón de sobrecargas innecesarias fueron. Por otro lado, quedó en desuso con C ++ 11 y se eliminará con C ++ 17. std::mem_fun
Max Truxa

@Danh Eso es exactamente de lo que estoy hablando;) La primera sobrecarga "básica" sigue ahí template<class R, class T> unspecified mem_fn(R T::*);, y no desaparecerá.
Max Truxa

@ Dan Lee el DR con cuidado. El DR eliminó 12 de las 13 sobrecargas. Ese último no fue (y no lo será; ni en C ++ 11 ni en C ++ 14).
Max Truxa

1
¿Por qué el voto negativo? Cualquier otra respuesta dice que debes vincular la instancia de clase. Si está creando un sistema de enlace para la reflexión o secuencias de comandos, no querrá hacerlo. Este método alternativo es válido y relevante para algunas personas.
Greg

Gracias Danh, he editado con algunos comentarios sobre interfaces pasadas y futuras relacionadas.
Greg

3

Desafortunadamente, C ++ no le permite obtener directamente un objeto invocable que se refiera a un objeto y a una de sus funciones miembro. &Foo::doSomethingle proporciona un "puntero a la función miembro" que se refiere a la función miembro pero no al objeto asociado.

Hay dos formas de evitar esto, una es usar std::bindpara vincular el "puntero a la función miembro" al thispuntero. La otra es usar una lambda que capture el thispuntero y llame a la función miembro.

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

Preferiría lo último.

Con g ++ al menos unir una función miembro a esto dará como resultado un objeto de tres punteros de tamaño, y asignar esto a un std::functiondará como resultado una asignación de memoria dinámica.

Por otro lado, una lambda que captura thissolo tiene un tamaño de puntero, asignarla a un std::functionno dará como resultado una asignación de memoria dinámica con g ++.

Si bien no he verificado esto con otros compiladores, sospecho que se encontrarán resultados similares allí.


1

Puede usar functors si desea un control menos genérico y más preciso debajo del capó. Ejemplo con mi win32 api para reenviar el mensaje api de una clase a otra clase.

IListener.h

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

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

};

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

Buena suerte y gracias a todos por compartir conocimientos.


0

Puedes evitar std::bindhacer esto:

std::function<void(void)> f = [this]-> {Foo::doSomething();}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.