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.


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

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

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

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


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


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

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

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.


-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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.