Como posso passar uma função de membro em que uma função livre é esperada?

122

A questão é a seguinte: considere este pedaço de código:

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}

Como posso usar os a's aClass::testcomo um argumento para function1? Estou preso em fazer isso.

Eu gostaria de acessar um membro da classe.

Jorge Leitao
fonte
1
Dê uma olhada nesta resposta stackoverflow.com/questions/2402579/... e também este C ++ FAQ parashift.com/c++-faq/pointers-to-members.html
amdn
16
Isso absolutamente não é uma duplicata (pelo menos não da questão específica que está vinculada). Essa pergunta é sobre como declarar um membro que é um ponteiro para uma função; trata-se de como passar um ponteiro para uma função membro não estática como parâmetro.
CarLuva

Respostas:

144

Não há nada de errado em usar ponteiros de função. No entanto, ponteiros para funções-membro não estáticas não são como ponteiros de funções normais: as funções-membro precisam ser chamadas em um objeto que é passado como um argumento implícito para a função. A assinatura da sua função de membro acima é, portanto,

void (aClass::*)(int, int)

em vez do tipo que você tenta usar

void (*)(int, int)

Uma abordagem poderia consistir em fazer o membro funcionar static em cujo caso ele não requer qualquer objeto a ser chamado e você pode usá-lo com o tipo void (*)(int, int).

Se você precisar acessar qualquer membro não estático da sua classe e precisar void*usar ponteiros de função, por exemplo, porque a função faz parte de uma interface C, sua melhor opção é sempre passar um para sua função, recebendo os ponteiros de função e chamando seu membro através de uma função de encaminhamento que obtém um objeto dovoid* e depois chama a função de membro.

Em uma interface C ++ adequada, você pode querer que sua função use argumentos modelados para que objetos de função usem tipos de classe arbitrários. Se o uso de uma interface de modelo é indesejável, você deve usar algo como std::function<void(int, int)>: você pode criar um objeto de função que possa ser chamado por eles, por exemplo, usandostd::bind() .

As abordagens de segurança de tipo usando um argumento de modelo para o tipo de classe ou um adequado std::function<...>são preferíveis a usar umvoid* interface, pois elas removem o potencial de erros devido a uma conversão para o tipo errado.

Para esclarecer como usar um ponteiro de função para chamar uma função de membro, aqui está um exemplo:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}
Dietmar Kühl
fonte
Ok, eu gosto desta resposta! Você pode especificar o que você quer dizer com "ligar para seu membro através de uma função de encaminhamento que obtém um objeto do vazio * e depois chama a função de membro" ou compartilhar um link útil para ele? Obrigado
Jorge Leitao
Eu acho que entendi. (Eu editei sua postagem) Obrigado pela explicação e pelo exemplo, realmente útil. Apenas para confirmar: para cada função de membro que eu quero apontar, tenho que criar um encaminhador. Certo?
Jorge Leitao
Bem, sim, mais ou menos. Dependendo da eficácia do uso de modelos, você pode criar modelos de encaminhamento que podem funcionar com diferentes classes e funções-membro. Como fazer isso seria uma função separada, eu acho ;-)
Dietmar Kühl
1
Não gosto desta resposta porque ela usa void*, o que significa que você pode obter bugs muito desagradáveis, porque não é mais digitado.
Superlokkus 22/09/2015
@Superlokkus: você pode nos esclarecer com uma alternativa?
Dietmar Kühl
88

A resposta de @Pete Becker é boa, mas você também pode fazê-lo sem passar a classinstância como um parâmetro explícito function1no C ++ 11:

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}
Matt Phillips
fonte
1
Está void function1(std::function<void(int, int)>)correto?
Deqing
2
Você precisa dar ao argumento da função um nome de variável e, em seguida, o nome da variável é o que você realmente passa. Então: void function1(std::function<void(int, int)> functionToCall)e então functionToCall(1,1);. Tentei editar a resposta, mas alguém a rejeitou por não ter sentido por algum motivo. Vamos ver se é votado em algum momento.
Dorky Engenheiro
1
@DorkyEngineer Isso é muito estranho, acho que você deve estar certo, mas não sei como esse erro passou despercebido por tanto tempo. Enfim, editei a resposta agora.
Matt Phillips
2
Eu encontrei este post dizendo que há uma severa penalidade de desempenho do std :: function.
kevin
2
@ Kevin, você pode revisar seu comentário, pois as respostas dessa postagem mostraram uma falha no benchmark.
Jorge Leitao
54

Um ponteiro para a função de membro é diferente de um ponteiro para a função. Para usar uma função de membro através de um ponteiro, você precisa de um ponteiro (obviamente) e de um objeto para aplicá-lo. Portanto, a versão apropriada function1seria

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

e para chamá-lo:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);
Pete Becker
fonte
1
Muito obrigado. Acabei de descobrir que os colchetes contam aqui: function1 (& (aClass :: test), a) funciona com o MSVC2013, mas não com o gcc. gcc precisa do & diretamente na frente do nome da classe (que eu acho confuso, porque o operador & leva o endereço da função, não da classe)
kritzel_sw
Pensei functionna void (aClass::*function)(int, int)era um tipo, por causa disso é um tipo de typedef void (aClass::*function)(int, int).
Olumide
@Olumide - typedef int X;define um tipo; int X;cria um objeto.
Pete Becker
11

Desde 2011, se você pode alterar function1, faça o seguinte:

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}

( demonstração ao vivo )

Observe também que eu corrigi sua definição de objeto quebrado ( aClass a();declara uma função).

Raças de leveza em órbita
fonte
2

Eu fiz uma pergunta semelhante ( C ++ openframeworks passando vazio de outras classes ), mas a resposta que encontrei foi mais clara, então aqui a explicação para futuros registros:

é mais fácil usar std :: function como em:

 void draw(int grid, std::function<void()> element)

e depois chame como:

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

ou ainda mais fácil:

  grid.draw(12, [&]{a.draw()});

onde você cria um lambda que chama o objeto capturando-o por referência

Nicola Bertelloni
fonte
1
Dado que isso está marcado como CPP, acho que essa é a solução mais limpa. Poderíamos usar uma referência constante em std::functionno drawentanto, em vez de copiá-lo em todas as chamadas.
Sohaib
2
O espaço reservado não deve ser usado neste exemplo, pois a função não usa nenhum parâmetro.
Kriz #
1

Eu fiz o membro funcionar como estático e todos os trabalhos:

#include <iostream>

class aClass
{
public:
    static void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(10,12,test);
    function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?

    getchar();return 0;
}
mathengineer
fonte
Não apenas resolva a questão principal "Como posso usar o aClass :: test do a como argumento para function1?" mas também é recomendável evitar modificar variáveis ​​privadas de classe usando código externo à classe
mathengineer
1

Não sei por que essa solução incrivelmente simples foi descartada:

#include <stdio.h>

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}

Resultado:

1 - 1 = 0
1 + 1 = 2
hLk
fonte
0

Se você realmente não precisa usar a instância a (ou seja, você pode torná-la estática como a resposta do @mathengineer ), basta passar uma lambda não capturada. (que deteriora para funcionar como ponteiro)


#include <iostream>

class aClass
{
public:
   void aTest(int a, int b)
   {
      printf("%d + %d = %d", a, b, a + b);
   }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

Wandbox


nota: se aClassé caro construir ou tem efeito colateral, isso pode não ser um bom caminho.

maçã maçã
fonte
-1

Você pode parar de bater com a cabeça agora. Aqui está o invólucro da função de membro para suportar funções existentes , recebendo funções C simples como argumentos. thread_localdiretiva é a chave aqui.

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

Por favor, comente sobre quaisquer problemas com essa abordagem.

Outras respostas falham ao chamar funções simples existentesC : http://cpp.sh/8exun

Necktwi
fonte
3
Portanto, em vez de usar std :: bind ou um lambda para agrupar a instância, você conta com uma variável global. Não vejo nenhuma vantagem nessa abordagem em comparação com as outras respostas.
Super
@super, Outras respostas não podem chamar funções existentes, tendo Cfunções simples como argumentos.
Necktwi
2
A questão é como chamar uma função de membro. Passar em uma função livre já está funcionando para o OP na questão. Você também não está passando nada. Há apenas funções codificadas aqui e um ponteiro Foo_ global. Como seria essa escala se você quiser chamar uma função-membro diferente? Você precisaria reescrever as funções subjacentes ou usar funções diferentes para cada destino.
Super