Pergunta

Eu estou usando uma API que me obriga a passar um ponteiro função como um callback. Eu estou tentando usar este API da minha classe, mas eu estou recebendo erros de compilação.

Aqui está o que eu fiz de meu construtor:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Esta não compila - eu recebo o seguinte erro:

Erro 8 de erro C3867: 'CLoggersInfra :: RedundencyManagerCallBack': chamada de função faltando lista de argumentos; usar '& CLoggersInfra :: RedundencyManagerCallBack' para criar um ponteiro para membro

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

Todas as sugestões / explicação para isso ??

Eu estou usando VS2008.

Graças !!

Foi útil?

Solução

Isso não funciona porque um ponteiro de função membro não pode ser tratado como um ponteiro de função normal, porque ele espera um "presente" argumento de objeto.

Em vez disso, você pode passar uma função membro estático como segue, que são como funções normais não-associados a este respeito:

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

A função pode ser definida como se segue

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

Outras dicas

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 abaixo.

O compilador é correto sugiro que você use & CLoggersInfra :: RedundencyManagerCallBack. Primeiro, se RedundencyManagerCallBack é uma função membro, a própria função não pertence a qualquer instância específica do CLoggersInfra classe. Pertence à própria classe. Se você já chamou uma função de classe estática antes, você pode ter notado que você use a mesma sintaxe SomeClass :: SomeMemberFunction. Desde a própria função é 'estático' no sentido de que ele pertence à classe, em vez de uma instância específica, você pode usar a mesma sintaxe. A 'e' é necessário porque tecnicamente falando você não passar funções diretamente - funções não são objetos reais em C ++. Em vez disso você está passando tecnicamente o endereço de memória para a função, isto é, um ponteiro para onde as instruções da função começam na memória. A conseqüência é a mesma, porém, você está efetivamente 'passando uma função' como um parâmetro.

Mas isso só de metade do problema neste caso. Como eu disse, RedundencyManagerCallBack a função não 'pertencem' a qualquer instância particular. Mas parece que você quer passá-lo como uma chamada de retorno com uma instância específica em mente. Para entender como fazer isso, você precisa entender o que funções membro realmente são:. Funções não definidas-em-qualquer-classe regulares com um parâmetro extra escondido

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 << an_a_object.data; // Prints 10!

Quantos parâmetros faz A :: foo take? Normalmente diríamos 1. Mas sob o capô, foo realmente leva 2. Olhando para a definição de A :: foo, ele precisa de uma instância específica de um para que o ponteiro 'this' para ser significativo (as necessidades do compilador para saber o que ' isto é). A maneira como você normalmente especificar o que deseja 'isto' para ser é através do MyObject.MyMemberFunction sintaxe (). Mas esta é apenas açúcar sintático para passar o endereço do MyObject como o primeiro parâmetro para MyMemberFunction. Da mesma forma, quando nós declaramos funções membro dentro de definições de classe que não colocar 'this' na lista de parâmetros, mas este é apenas um presente dos designers de idioma para economizar digitação. Em vez disso você tem que especificar que uma função membro é estático de optar por ele automaticamente recebendo o extra 'esta' parâmetro. Se o compilador C ++ traduziu o exemplo acima para o código C (o compilador C original ++ realmente funcionou dessa forma), seria algo provavelmente escrita assim:

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

Voltando ao seu exemplo, agora há um problema óbvio. 'Init' quer um ponteiro para uma função que leva um parâmetro. Mas & CLoggersInfra :: RedundencyManagerCallBack é um ponteiro para uma função que recebe dois parâmetros, o parâmetro normal, de e o segredo 'isto' parâmetro. Assim, por que você ainda está recebendo um erro do compilador (como uma nota lateral: Se você já usou Python, este tipo de confusão é por isso que um parâmetro 'self' é necessário para todas as funções membro).

O prolixo maneira de lidar com isso é criar um objeto especial que contém um ponteiro para a instância que você quer e tem uma função membro chamado algo como 'run' ou 'executar' (ou sobrecargas o '()' Operador) que leva os parâmetros para a função de membro, e simplesmente chama a função de membro com esses parâmetros na instância armazenado. Mas isso exigiria que você mude 'Init' para tomar o seu objeto especial em vez de um ponteiro de função cru, e parece que Init é o código de outra pessoa. E fazendo uma classe especial para cada vez que este problema surge vai levar a inchaço de código.

Então, agora, finalmente, a solução boa, boost :: bind e boost :: função, a documentação de cada você pode encontrar aqui:

boost :: bind docs , docs boost :: função

boost :: bind permite levar uma função e um parâmetro para essa função, e fazer uma nova função na qual esse parâmetro for 'fechado' no lugar. Então, se eu tenho uma função que adiciona dois números inteiros, eu posso usar boost :: bind para fazer uma nova função onde um dos parâmetros está bloqueada para dizer 5. Esta nova função só terá um parâmetro inteiro, e sempre vai adicionar 5 especificamente a ele. Usando esta técnica, você pode 'lock in' a 'this' parâmetro oculto para ser uma instância de classe particular, e gerar uma nova função que leva apenas um parâmetro, assim como você deseja (note que o parâmetro oculto é sempre o primeira parâmetro e os parâmetros normais vêm em ordem após ele). Olhe para o impulso :: bind docs para exemplos, eles ainda especificamente discutir a usá-lo para funções de membro. Tecnicamente não é uma função padrão chamado std :: bind1st que você poderia usar também, mas boost :: bind é mais geral.

Claro, não é apenas mais uma captura. boost :: bind vai fazer um bom boost :: função para você, mas isso ainda não é tecnicamente um ponteiro de função crua como Init provavelmente quer. Felizmente, boost fornece uma maneira de impulsionar convertido :: função da ponteiros crus, como documentado em StackOverflow aqui . Como implementa isso está além do escopo desta resposta, porém é interessante também.

Não se preocupe se isso parece ridiculamente difícil -. Suas perguntas cruza vários de C ++ 's cantos mais escuros, e boost :: bind é extremamente útil uma vez que você aprender

C ++ 11 atualização: Em vez de boost :: bind agora você pode usar uma função lambda que captura 'isto'. Esta é, basicamente, ter o compilador gerar a mesma coisa para você.

Esta resposta é uma resposta a um comentário anterior e não funciona com VisualStudio 2008, mas deve ser preferido com compiladores mais recentes.


Enquanto isso, você não tem que usar um ponteiro void mais e também não há necessidade de aumento desde std::bind e std::function estão disponíveis. Um vantagem (em comparação com ponteiros void) é a segurança de tipos, desde o tipo de retorno e os argumentos são explicitamente usando std::function:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

Em seguida, você pode criar o ponteiro de função com std::bind e passá-lo para Init:

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

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

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> 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 passar dinamicamente argumentos para a chamada de retorno ( por exemplo, isso permite o uso de return f("MyString"); em Init se f tem um parâmetro string).

O argumento faz Init take? Qual é a nova mensagem de erro?

Método ponteiros em C ++ são um pouco difícil de usar. Além do ponteiro método em si, você também precisará fornecer um ponteiro de instância (no seu caso this). Talvez Init espera-lo como um argumento separado?

Um ponteiro para uma função de membro de classes não é o mesmo que um ponteiro para uma função. Um membro da classe leva um argumento extra implícito (o este ponteiro), e usa uma convenção de chamada diferente.

Se o seu API espera que uma função não-membro de retorno, que é o que você tem que passar para ele.

É m_cRedundencyManager capaz de usar as funções de membro? A maioria das chamadas de retorno são configurados para utilizar as funções regulares ou funções de membro estático. Dê uma olhada na desta página em C ++ FAQ Lite para mais informações.

Update: A declaração da função que forneceu mostra que m_cRedundencyManager está esperando uma função da forma: void yourCallbackFunction(int, void *). funções membro são, portanto, inaceitável como chamadas de retorno, neste caso. A função de membro estático pode de trabalho, mas se isso é inaceitável no seu caso, o código a seguir também funcionaria. Note que ele usa um elenco mal 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);
}

Eu posso ver que a inicialização 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 *);

Este pergunta e resposta do < a href = "https://isocpp.org/wiki/faq" rel = "nofollow noreferrer"> C ++ FAQ Lite tampas sua pergunta e as considerações envolvidas na resposta bastante bem, eu acho. trecho curto a partir da página web I ligados:

Do not.

Porque uma função membro não tem sentido sem um objeto para invocar -lo, você não pode fazer isso diretamente (se o X Window System foi reescrito em C ++, ele provavelmente iria passar referências a objetos ao redor, não apenas ponteiros para funções; naturalmente os objetos que encarnam o função pretendida e, provavelmente, muito mais).

Necromancing.
Eu acho que as respostas até agora são um pouco claro.

Vamos dar um exemplo:

Suposto você tem uma matriz de pixels (array de ARGB int8_t valores)

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

Agora você deseja gerar um PNG. Para fazer isso, você chamar a função toJpeg

bool ok = toJpeg(writeByte, pixels, width, height);

onde writeByte é uma função de chamada de retorno

void writeByte(unsigned char oneByte)
{
    fputc(oneByte, output);
}

O problema aqui: saída de FILE * tem que ser uma variável global.
Muito ruim se você estiver em um ambiente de vários segmentos (por exemplo, um http-server).

Então, você precisa de alguma forma para tornar a saída uma variável não-global, mantendo a assinatura de retorno de chamada.

A solução imediata que molas em mente é um fecho, que pode emular usando uma classe com uma função de membro.

class BadIdea {
private:
    FILE* m_stream;
public:
    BadIdea(FILE* stream)  {
        this->m_stream = stream;
    }

    void writeByte(unsigned char oneByte){
            fputc(oneByte, this->m_stream);
    }

};

E, em seguida, fazer

FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);

bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);

No entanto, ao contrário das expectativas, isso não funciona.

A razão é, as funções de membro C ++ são meio implementado como funções C # extensão.

Então você tem

class/struct BadIdea
{
    FILE* m_stream;
}

e

static class BadIdeaExtensions
{
    public static writeByte(this BadIdea instance, unsigned char oneByte)
    {
         fputc(oneByte, instance->m_stream);
    }

}

Então, quando você quiser chamar writeByte, você precisa passar não só o endereço de writeByte, mas também o endereço do BadIdea instância.

Então, quando você tem um typedef para o procedimento writeByte, e parece que esta

typedef void (*WRITE_ONE_BYTE)(unsigned char);

E você tem uma assinatura writeJpeg parecida com esta

bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
 width, uint32_t height))
    { ... }

é fundamentalmente impossível passar uma função membro de dois endereços para um ponteiro de função de um endereço (sem modificar writeJpeg), e não há maneira de contornar isso.

A próxima melhor coisa que você pode fazer em C ++, está usando uma função lambda:

FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);

No entanto, porque lambda não está fazendo nada diferente, do que passar uma instância de uma classe oculta (como o "BadIdea" de classe), é necessário modificar a assinatura do writeJpeg.

A vantagem de lambda sobre uma classe Manual, é que você só precisa mudar um typedef

typedef void (*WRITE_ONE_BYTE)(unsigned char);

para

using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 

E então você pode deixar tudo intocado.

Você também pode usar std :: bind

auto f = std::bind(&BadIdea::writeByte, &foobar);

Mas isso, por trás da cena, apenas cria uma função lambda, que, em seguida, também precisa da mudança na typedef.

Então, não, não há nenhuma maneira de passar uma função de membro para um método que requer uma função de ponteiro estático.

Mas lambdas são o caminho mais fácil ao redor, desde que você tenha controle sobre a fonte.
Caso contrário, você está sem sorte.
Não há nada que você pode fazer com C ++.

Nota:
std :: função requer #include <functional>

No entanto, desde C ++ permite que você use C, bem, você pode fazer isso com libffcall em C simples, se você não se importa que liga uma dependência.

Fazer download libffcall do GNU (pelo menos no ubuntu, não use o pacote fornecido-distro - ele está quebrado), descompactação.

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

main.c:

#include <callback.h>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    return EXIT_SUCCESS;
}

E se você usar CMake, adicione a biblioteca vinculador após add_executable

add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
        IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)

ou você pode apenas dinamicamente ligar libffcall

target_link_libraries(BitmapLion ffcall)

Nota:
Você pode querer incluir os cabeçalhos libffcall e bibliotecas, ou criar um projeto cmake com o conteúdo do libffcall.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top