Usando objetos std :: function genéricos com funções-membro em uma classe

170

Para uma classe, desejo armazenar alguns ponteiros de função para funções-membro da mesma classe em um objeto de maparmazenamento std::function. Mas eu falhei logo no começo com este código:

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

Eu recebo error C2064: term does not evaluate to a function taking 0 argumentsem xxcallobjcombinado com alguns erros modelo instanciação estranhas. Atualmente, estou trabalhando no Windows 8 com o Visual Studio 2010/2011 e no Win 7 com o VS10, também falha. O erro deve ser baseado em algumas regras C ++ estranhas que eu não sigo

Christian Ivicevic
fonte

Respostas:

301

Uma função de membro não estática deve ser chamada com um objeto. Ou seja, sempre passa implicitamente "esse" ponteiro como argumento.

Como sua std::functionassinatura especifica que sua função não aceita argumentos ( <void(void)>), você deve vincular o primeiro (e o único) argumento.

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

Se você deseja vincular uma função a parâmetros, precisará especificar espaços reservados:

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

Ou, se o seu compilador suportar lambdas C ++ 11:

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

(Eu não tenho um compilador compatível com C ++ 11 no momento , então não posso verificar este.)

Alex B
fonte
1
Como não sou dependente do impulso, usarei expressões lambda;) No entanto, obrigado!
Christian Ivicevic
3
@AlexB: Boost.Bind não usa ADL para os espaços reservados, coloca-os em um espaço para nome anônimo.
precisa saber é o seguinte
46
Eu recomendo para evitar a captura mundial [=] e utilização [desta] para torná-lo mais claro o que é capturado (Scott Meyers - Capítulo ++ eficaz Modern C 6. artigo 31 - modos de evitar a captura padrão)
Max Raskin
5
Basta adicionar uma pequena dica: ponteiro função de membro pode ser convertido implicitamente std::function, com extras thiscomo é primeiro parâmetro, comostd::function<void(Foo*, int, int)> = &Foo::doSomethingArgs
landerlyoung
@landerlyoung: adicione o nome da função say como "f" acima para corrigir a sintaxe da amostra. Se você não precisar do nome, poderá usar o mem_fn (& Foo :: doSomethingArgs).
Val
80

Ou você precisa

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

para que você possa chamá-lo em qualquer instância ou precisar vincular uma instância específica, por exemplo this

std::function<void(void)> f = std::bind(&Foo::doSomething, this);
Armen Tsirunyan
fonte
Obrigado por esta ótima resposta: D Exatamente o que eu preciso, não consegui encontrar como especializar uma função std :: para chamar uma função membro em qualquer instância de classe.
Penelope
Isso compila, mas é padrão? Você tem certeza de que o primeiro argumento é this?
sudo rm -rf slash
@ sudorm-rfslash yes, you are #
Armen Tsirunyan
Obrigado pela resposta @ArmenTsirunyan ... onde no padrão posso procurar essas informações?
sudo rm -rf slash
13

Se você precisar armazenar uma função de membro sem a instância da classe, poderá fazer algo assim:

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

Como seria o tipo de armazenamento sem auto ? Algo assim:

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

Você também pode passar esse armazenamento de função para uma ligação de função padrão

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

Notas passadas e futuras: Uma interface antiga std :: mem_func existia, mas foi descontinuada. Existe uma proposta, pós-C ++ 17, para tornar o ponteiro para funções-membro que pode ser chamado . Isso seria muito bem-vindo.

Greg
fonte
@Danh nãostd::mem_fn foi removido; um monte de sobrecargas desnecessárias foram. Por outro lado, foi preterido no C ++ 11 e será removido no C ++ 17. std::mem_fun
precisa saber é o seguinte
@ Danh É exatamente disso que estou falando;) A primeira sobrecarga "básica" ainda está lá: template<class R, class T> unspecified mem_fn(R T::*);e não vai desaparecer.
precisa saber é o seguinte
@ Danh Leia atentamente o DR . 12 das 13 sobrecargas foram removidas pelo DR. Esse último não foi (e não será; nem em C ++ 11 ou C ++ 14).
precisa saber é o seguinte
1
Por que o voto negativo? Todas as outras respostas disseram que você deve vincular a instância da classe. Se você estiver criando um sistema de ligação para reflexão ou script, não desejará fazer isso. Este método alternativo é válido e relevante para algumas pessoas.
Greg
Obrigado Danh, editei com alguns comentários sobre interfaces passadas e futuras relacionadas.
Greg
3

Infelizmente, o C ++ não permite que você obtenha diretamente um objeto que pode ser chamado referente a um objeto e a uma de suas funções membro. &Foo::doSomethingfornece um "ponteiro para a função de membro" que se refere à função de membro, mas não ao objeto associado.

Há duas maneiras de contornar isso: uma é usar std::bindpara vincular o "ponteiro à função de membro" ao thisponteiro. O outro é usar um lambda que captura o thisponteiro e chama a função de membro.

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

Eu preferiria o último.

Com o g ++, pelo menos, vincular uma função de membro a isso resultará em um objeto com três ponteiros de tamanho, atribuir isso a um std::functionresultará em alocação dinâmica de memória.

Por outro lado, um lambda que captura thisé apenas um ponteiro em tamanho, atribuí-lo a um std::functionnão resultará em alocação dinâmica de memória com g ++.

Embora eu não tenha verificado isso com outros compiladores, suspeito que resultados semelhantes serão encontrados lá.

plugwash
fonte
1

Você pode usar functors se desejar um controle menos genérico e mais preciso sob o capô. Exemplo com minha API do win32 para encaminhar a mensagem da API de uma classe para outra 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);
        }

};

Princípios 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);
    }
};

Boa sorte e obrigado a todos por compartilhar conhecimento.


fonte
0

Você pode evitar std::bindfazer isso:

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