Como posso passar uma function de membro de class como um retorno de chamada?

Estou usando uma API que requer que eu passe um ponteiro de function como um retorno de chamada. Estou tentando usar essa API da minha class, mas estou recebendo erros de compilation.

Aqui está o que eu fiz do meu construtor:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack); 

Isso não compila – recebo o seguinte erro:

Erro 8 Erro C3867: ‘CLoggersInfra :: RedundencyManagerCallBack’: lista de argumentos ausentes da chamada de function; use ‘& CLoggersInfra :: RedundencyManagerCallBack’ para criar um ponteiro para o membro

Eu tentei a sugestão de usar o &CLoggersInfra::RedundencyManagerCallBack – não funcionou para mim.

Alguma sugestão / explicação para isso?

Estou usando o VS2008.

Obrigado!!

    Como você mostrou agora, sua function init aceita uma function que não é membro. então faça assim:

     static void Callback(int other_arg, void * this_pointer) { CLoggersInfra * self = static_cast(this_pointer); self->RedundencyManagerCallBack(other_arg); } 

    e chame Init com

     m_cRedundencyManager->Init(&CLoggersInfra::Callback, this); 

    Isso funciona porque um ponteiro de function para uma function de membro estático não é um ponteiro de function de membro e, portanto, pode ser tratado como apenas um ponteiro para uma function livre.

    Esta é uma pergunta simples, mas a resposta é surpreendentemente complexa. A resposta curta é que você pode fazer o que você está tentando fazer com std :: bind1st ou boost :: bind. A resposta mais longa está abaixo.

    O compilador está correto em sugerir que você use o & CLoggersInfra :: RedundencyManagerCallBack. Primeiro, se RedundencyManagerCallBack for uma function de membro, a function em si não pertence a nenhuma instância específica da class CLoggersInfra. Pertence à própria class. Se você já chamou uma function de class estática antes, você deve ter notado que você usa a mesma syntax SomeClass :: SomeMemberFunction. Como a function em si é ‘estática’ no sentido de pertencer à class e não a uma instância específica, você usa a mesma syntax. O ‘&’ é necessário porque, tecnicamente falando, você não passa funções diretamente – funções não são objects reais em C ++. Em vez disso, você está tecnicamente passando o endereço de memory para a function, isto é, um ponteiro para onde as instruções da function começam na memory. A consequência é a mesma, porém, você está efetivamente ‘passando uma function’ como parâmetro.

    Mas isso é apenas metade do problema nesta instância. Como eu disse, RedundencyManagerCallBack a function não ‘pertence’ a qualquer instância particular. Mas parece que você quer passar isso como um retorno de chamada com uma determinada instância em mente. Para entender como fazer isso, você precisa entender quais funções membro realmente são: funções regulares não-definidas-em-qualquer-class com um parâmetro extra oculto.

    Por exemplo:

    class A { public: A() : data(0) {} void foo(int addToData) { this->data += addToData; } int data; }; ... A an_a_object; an_a_object.foo(5); A::foo(&an_a_object, 5); // This is the same as the line above! std::cout 

    Quantos parâmetros o A :: foo leva? Normalmente nós diríamos 1. Mas sob o capô, o foo realmente toma 2. Olhando para a definição de A :: foo, ele precisa de uma instância específica de A para que o ponteiro ‘this’ seja significativo (o compilador precisa saber o que ‘ isto é). A maneira que você normalmente especifica o que você quer ‘isto’ é através da syntax MyObject.MyMemberFunction (). Mas isso é apenas açúcar sintático para passar o endereço do MyObject como o primeiro parâmetro para MyMemberFunction. Da mesma forma, quando declaramos as funções de membro dentro das definições de class, não colocamos ‘this’ na lista de parâmetros, mas isso é apenas um presente dos designers de linguagem para economizar na digitação. Em vez disso, você precisa especificar que uma function de membro é estática para desativá-la automaticamente, obtendo o parâmetro ‘this’ extra. Se o compilador C ++ traduzisse o exemplo acima para o código C (o compilador C ++ original funcionava dessa forma), provavelmente escreveria algo assim:

    
     struct A {
         dados int;
     };
    
     void a_init (A * to_init)
     {
         to_init-> data = 0;
     }
    
     void a_foo (A * isso, int addToData)
     { 
         this-> data + = addToData;
     }
    
     ...
    
     Um an_a_object;
     a_init (0);  // Antes da chamada do construtor estar implícita
     a_foo (& an_a_object, 5);  // Usado para ser an_a_object.foo (5);
    
    

    Voltando ao seu exemplo, agora há um problema óbvio. ‘Init’ quer um ponteiro para uma function que usa um parâmetro. Mas & CLoggersInfra :: RedundencyManagerCallBack é um ponteiro para uma function que usa dois parâmetros, é o parâmetro normal e o parâmetro secreto ‘this’. Assim, por que você ainda está recebendo um erro de compilador (como uma observação: Se você já usou Python, esse tipo de confusão é o motivo pelo qual um parâmetro ‘self’ é requerido para todas as funções de membro).

    A maneira detalhada de lidar com isso é criar um object especial que contenha um ponteiro para a instância que você quer e que tenha uma function de membro chamada algo como ‘executar’ ou ‘executar’ (ou sobrecarregar o operador ‘()’) para a function de membro e simplesmente chama a function de membro com esses parâmetros na instância armazenada. Mas isso exigiria que você alterasse ‘Init’ para pegar seu object especial em vez de um ponteiro de function raw, e soa como se o Init fosse o código de outra pessoa. E fazer uma aula especial para cada vez que este problema surgir levará ao inchaço do código.

    Então agora, finalmente, a boa solução, boost :: bind e boost :: function, a documentação para cada um que você pode encontrar aqui:

    boost :: bind docs , documentação do boost :: function

    boost :: bind permitirá que você tome uma function, e um parâmetro para essa function, e faça uma nova function onde o parâmetro está ‘bloqueado’ no lugar. Então, se eu tenho uma function que adiciona dois inteiros, eu posso usar boost :: bind para fazer uma nova function onde um dos parâmetros está bloqueado para dizer 5. Esta nova function terá apenas um parâmetro inteiro, e sempre adicionará 5 especificamente para isso. Usando esta técnica, você pode ‘travar’ o parâmetro ‘this’ oculto para ser uma instância de class particular, e gerar uma nova function que leva apenas um parâmetro, exatamente como você quer (note que o parâmetro oculto é sempre o primeiro parâmetro, e os parâmetros normais vêm em ordem depois disso). Olhe para o boost :: bind docs para exemplos, eles ainda discutem especificamente usando para funções de membro. Tecnicamente, existe uma function padrão chamada std :: bind1st que você pode usar também, mas boost :: bind é mais geral.

    Claro, há apenas mais uma captura. boost :: bind irá fazer um bom boost :: function para você, mas isso ainda não é tecnicamente um ponteiro de function raw como Init provavelmente quer. Felizmente, o boost fornece uma maneira de converter boost :: function para pointers brutos, conforme documentado no StackOverflow aqui . Como isso implementa está além do escopo desta resposta, embora seja interessante também.

    Não se preocupe se isso parecer ridiculamente difícil – sua pergunta intercepta vários dos cantos mais escuros de C ++, e boost :: bind é incrivelmente útil quando você o aprende.

    Esta resposta é uma resposta a um comentário acima e não funciona com o Visual Studio 2008, mas deve ser preferida com compiladores mais recentes.


    Enquanto isso, você não precisa mais usar um ponteiro vazio e também não há necessidade de reforço, pois std::bind e std::function estão disponíveis. Uma vantagem (em comparação com pointers nulos) é a segurança de tipo, já que o tipo de retorno e os argumentos são declarados explicitamente usando std::function :

     // std::function void Init(std::function f); 

    Então você pode criar o ponteiro de function com std::bind e passá-lo para o Init:

     auto cLoggersInfraInstance = CLoggersInfra(); auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance); Init(callback); 

    Exemplo completo para usar std::bind com membro, membros estáticos e funções não membro:

     #include  #include  #include  class RedundencyManager // incl. Typo ;-) { public: // std::function std::string Init(std::function f) { return f(); } }; class CLoggersInfra { private: std::string member = "Hello from non static member callback!"; public: static std::string RedundencyManagerCallBack() { return "Hello from static member callback!"; } std::string NonStaticRedundencyManagerCallBack() { return member; } }; std::string NonMemberCallBack() { return "Hello from non member function!"; } int main() { auto instance = RedundencyManager(); auto callback1 = std::bind(&NonMemberCallBack); std::cout < < instance.Init(callback1) << "\n"; // Similar to non member function. auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack); std::cout << instance.Init(callback2) << "\n"; // Class instance is passed to std::bind as second argument. // (heed that I call the constructor of CLoggersInfra) auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack, CLoggersInfra()); std::cout << instance.Init(callback3) << "\n"; } 

    Saída possível:

     Hello from non member function! Hello from static member callback! Hello from non static member callback! 

    Além disso, usando std::placeholders você pode dinamicamente passar argumentos para o retorno de chamada (por exemplo, isso permite o uso de return f("MyString"); em Init if f tem um parâmetro de string).

    Qual argumento a Init toma? Qual é a nova mensagem de erro?

    Ponteiros de método em C ++ são um pouco difíceis de usar. Além do ponteiro do método, você também precisa fornecer um ponteiro de instância (no seu caso, this ). Talvez Init espera isso como um argumento separado?

    É m_cRedundencyManager capaz de usar funções de membro? A maioria dos retornos de chamada é configurada para usar funções regulares ou funções de membro estático. Dê uma olhada nesta página em C ++ FAQ Lite para mais informações.

    Update: A declaração de function que você forneceu mostra que m_cRedundencyManager está esperando uma function do formulário: void yourCallbackFunction(int, void *) . As funções de membro são, portanto, inaceitáveis ​​como retornos de chamada nesse caso. Uma function de membro estático pode funcionar, mas se isso for inaceitável no seu caso, o código a seguir também funcionará. Note que ele usa um casting maléfico de void * .

     // in your CLoggersInfra constructor: m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this); 
     // in your CLoggersInfra header: void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr); 
     // in your CLoggersInfra source file: void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr) { ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i); } 

    Um ponteiro para uma function de membro de class não é o mesmo que um ponteiro para uma function. Um membro de class usa um argumento extra implícito (o ponteiro this ) e usa uma convenção de chamada diferente.

    Se sua API espera uma function de retorno de chamada de não membro, isso é o que você precisa transmitir a ela.

    Eu posso ver que o init tem a seguinte substituição:

    Init (CALLBACK_FUNC_EX callback_func, void * callback_parm)

    onde CALLBACK_FUNC_EX é typedef void (* CALLBACK_FUNC_EX) (int, void *);

    Esta pergunta e resposta do C ++ FAQ Lite cobre sua pergunta e as considerações envolvidas na resposta muito bem, eu acho. Snippet curto da página da Web que eu vinculei:

    Não faça

    Como uma function de membro não tem sentido sem um object para invocá-la, você não pode fazer isso diretamente (se o X Window System foi reescrito em C ++, provavelmente passaria referências a objects ao redor, não apenas pointers para funções; naturalmente os objects incorporaria a function necessária e provavelmente muito mais).