Come posso passare una funzione membro in cui è prevista una funzione libera?


122

La domanda è la seguente: considera questo pezzo di codice:

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

Come posso usare a's aClass::testcome argomento per function1? Sono bloccato nel farlo.

Vorrei accedere a un membro della classe.


1
Dai un'occhiata a questa risposta stackoverflow.com/questions/2402579/… e anche a questa FAQ C ++ parashift.com/c++-faq/pointers-to-members.html
amdn

16
Questo non è assolutamente un duplicato (almeno non della particolare domanda che è collegata). Questa domanda riguarda come dichiarare un membro che è un puntatore a una funzione; si tratta di come passare un puntatore a una funzione membro non statica come parametro.
CarLuva

Risposte:


144

Non c'è niente di sbagliato nell'usare i puntatori a funzione. Tuttavia, i puntatori a funzioni membro non statiche non sono come i normali puntatori a funzioni: le funzioni membro devono essere chiamate su un oggetto che viene passato come argomento implicito alla funzione. La firma della tua funzione membro sopra è, quindi

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

piuttosto che il tipo che si tenta di utilizzare

void (*)(int, int)

Un approccio potrebbe consistere nel far funzionare il membro, staticnel qual caso non richiede il richiamo di alcun oggetto e puoi usarlo con il tipo void (*)(int, int).

Se hai bisogno di accedere a qualsiasi membro non statico della tua classe e devi restare con i puntatori a funzione, ad esempio, perché la funzione fa parte di un'interfaccia C, la tua migliore opzione è quella di passare sempre a void*alla tua funzione prendendo puntatori di funzione e chiamando il membro tramite una funzione di inoltro che ottiene un oggetto da void*e quindi chiama la funzione membro.

In una corretta interfaccia C ++ potresti voler dare un'occhiata al fatto che la tua funzione prenda un argomento basato su modelli per gli oggetti funzione per usare tipi di classe arbitrari. Se l'utilizzo di un'interfaccia basata su modelli non è desiderabile, dovresti usare qualcosa di simile std::function<void(int, int)>: puoi creare un oggetto funzione richiamabile in modo appropriato per questi, ad esempio, using std::bind().

Gli approcci indipendenti dai tipi che utilizzano un argomento modello per il tipo di classe o uno adatto std::function<...>sono preferibili rispetto all'utilizzo di void*un'interfaccia poiché rimuovono il potenziale di errori dovuti a un cast al tipo sbagliato.

Per chiarire come utilizzare un puntatore a funzione per chiamare una funzione membro, ecco un esempio:

// 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, mi piace questa risposta! Puoi per favore specificare cosa intendi con "chiama il tuo membro tramite una funzione di inoltro che ottiene un oggetto dal vuoto * e poi chiama la funzione membro", o condividere un collegamento utile ad esso? Grazie
Jorge Leitao

Penso di aver capito. (Ho modificato il tuo post) Grazie per la spiegazione e l'esempio, davvero utile. Solo per confermare: per ogni funzione membro che voglio indicare, devo fare uno spedizioniere. Destra?
Jorge Leitao

Beh, sì, più o meno. A seconda dell'efficacia con cui stai utilizzando i modelli, puoi cavartela creando modelli di inoltro che possono funzionare con classi e funzioni membro diverse. Come farlo sarebbe una funzione separata, penso ;-)
Dietmar Kühl

1
Non mi piace questa risposta perché usa void*, il che significa che puoi ottenere bug molto fastidiosi, perché non è più digitata controllata.
Superlokkus

@Superlokkus: puoi illuminarci con un'alternativa?
Dietmar Kühl

88

La risposta di @Pete Becker va bene, ma puoi anche farlo senza passare l' classistanza come parametro esplicito function1in 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
È void function1(std::function<void(int, int)>)corretto?
Deqing

2
È necessario assegnare all'argomento della funzione un nome di variabile e quindi il nome della variabile è ciò che effettivamente si passa. Quindi: void function1(std::function<void(int, int)> functionToCall)e poi functionToCall(1,1);. Ho provato a modificare la risposta ma qualcuno l'ha rifiutata perché non aveva alcun senso per qualche motivo. Vedremo se prima o poi verrà votato.
Dorky Engineer

1
@DorkyEngineer È piuttosto strano, penso che tu abbia ragione, ma non so come quell'errore sia potuto passare inosservato per così tanto tempo. Comunque, ora ho modificato la risposta.
Matt Phillips

2
Ho trovato questo post in cui si dice che c'è una grave penalizzazione delle prestazioni da std :: function.
kevin

2
@kevin, potresti voler rivedere il tuo commento, poiché le risposte di quel post hanno mostrato un difetto nel benchmark.
Jorge Leitao

54

Un puntatore a una funzione membro è diverso da un puntatore a una funzione. Per poter utilizzare una funzione membro tramite un puntatore è necessario un puntatore ad essa (ovviamente) e un oggetto a cui applicarlo. Quindi la versione appropriata di function1sarebbe

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

e per chiamarlo:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);

1
Grazie mille. Ho appena scoperto che le parentesi contano qui: function1 (& (aClass :: test), a) funziona con MSVC2013 ma non con gcc. gcc ha bisogno del & direttamente davanti al nome della classe (che trovo confuso, perché l'operatore & prende l'indirizzo della funzione, non della classe)
kritzel_sw

Pensavo che functionin void (aClass::*function)(int, int)fosse un tipo, perché è un tipo in typedef void (aClass::*function)(int, int).
Olumide

@Olumide - typedef int X;definisce un tipo; int X;crea un oggetto.
Pete Becker

11

Dal 2011, se puoi cambiare function1, fallo in questo modo:

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

( demo live )

Si noti anche che ho corretto la definizione dell'oggetto danneggiato ( aClass a();dichiara una funzione).


2

Ho posto una domanda simile ( C ++ openframeworks passando void da altre classi ) ma la risposta che ho trovato era più chiara, quindi ecco la spiegazione per i record futuri:

è più facile usare std :: function come in:

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

e poi chiama come:

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

o anche più facile:

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

dove crei un lambda che chiama l'oggetto catturandolo per riferimento


1
Dato che questo è etichettato CPP, penso che questa sia la soluzione più pulita. Potremmo usare un riferimento costante a std::functionin drawsebbene invece di copiarlo a ogni chiamata.
Sohaib

2
Il segnaposto non deve essere utilizzato in questo esempio poiché la funzione non accetta alcun parametro.
kroiz

1

Ho fatto funzionare il membro come statico e tutto funziona:

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

Non solo risolve la domanda principale "Come posso usare aClass :: test come argomento per function1?" ma si consiglia anche di evitare di modificare le variabili private della classe utilizzando codice esterno alla classe
mathengineer

1

Non sono sicuro del motivo per cui questa soluzione incredibilmente semplice sia stata ignorata:

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

Produzione:

1 - 1 = 0
1 + 1 = 2

0

Se in realtà non hai bisogno di usare l'istanza a (cioè puoi renderla statica come la risposta di @mathengineer ) puoi semplicemente passare un lambda non acquisito. (che decade in puntatore a funzione)


#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è costoso da costruire o ha un effetto collaterale, questo potrebbe non essere un buon metodo.


-1

Puoi smetterla di sbattere la testa adesso. Ecco il wrapper per la funzione membro per supportare le funzioni esistenti che assumono semplici funzioni C come argomenti. thread_localla direttiva è la chiave qui.

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

Si prega di commentare eventuali problemi con questo approccio.

Altre risposte non riescono a chiamare leC funzioni normali esistenti : http://cpp.sh/8exun


3
Quindi, invece di usare std :: bind o lambda per avvolgere l'istanza, ti affidi a una variabile globale. Non vedo alcun vantaggio in questo approccio rispetto alle altre risposte.
super

@super, Altre risposte non possono chiamare funzioni esistenti che assumono Cfunzioni semplici come argomenti.
Necktwi

2
La domanda è come chiamare una funzione membro. Il passaggio di una funzione gratuita sta già funzionando per OP nella domanda. Inoltre non stai passando nulla. Ci sono solo funzioni hardcoded qui e un puntatore Foo_ globale. Come si ridimensionerebbe se si desidera chiamare una funzione membro diversa? Dovresti riscrivere le funzioni sottostanti o usarne di diverse per ogni destinazione.
super
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.