Wie kann ich eine Mitgliedsfunktion übergeben, bei der eine freie Funktion erwartet wird?

122

Die Frage lautet wie folgt: Betrachten Sie diesen Code:

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

Wie kann ich die a's aClass::testals Argument verwenden function1? Ich bin dabei festgefahren.

Ich möchte auf ein Mitglied der Klasse zugreifen.

Jorge Leitao
quelle
1
Werfen Sie einen Blick auf diese Antwort stackoverflow.com/questions/2402579/… und auch auf diese C ++ - FAQ parashift.com/c++-faq/pointers-to-members.html
amdn
16
Dies ist absolut kein Duplikat (zumindest nicht von der speziellen Frage, die verknüpft ist). Bei dieser Frage geht es darum, wie ein Mitglied deklariert wird, das ein Zeiger auf eine Funktion ist. Hier geht es darum, wie ein Zeiger als Parameter auf eine nicht statische Elementfunktion übergeben wird.
CarLuva

Antworten:

144

An der Verwendung von Funktionszeigern ist nichts auszusetzen. Zeiger auf nicht statische Elementfunktionen sind jedoch nicht wie normale Funktionszeiger: Elementfunktionen müssen für ein Objekt aufgerufen werden, das als implizites Argument an die Funktion übergeben wird. Die Signatur Ihrer Mitgliedsfunktion oben ist also

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

anstatt des Typs, den Sie verwenden möchten

void (*)(int, int)

Ein Ansatz könnte darin bestehen, das Element funktionsfähig zu machen. staticIn diesem Fall muss kein Objekt aufgerufen werden, und Sie können es mit dem Typ verwenden void (*)(int, int).

Wenn Sie irgendeine nicht-statische Member der Klasse zugreifen müssen und müssen Sie mit Funktionszeiger halten, zum Beispiel , weil die Funktion Teil einer C - Schnittstelle ist, die beste Wahl ist, immer ein Pass void*an Ihre Funktion Funktionszeiger und Anruf unter Ihr Mitglied über eine Weiterleitungsfunktion, die ein Objekt von der erhält void*und dann die Mitgliedsfunktion aufruft.

In einer richtigen C ++ - Schnittstelle möchten Sie vielleicht sehen, wie Ihre Funktion ein Vorlagenargument für Funktionsobjekte verwendet, um beliebige Klassentypen zu verwenden. Wenn die Verwendung einer Vorlagenschnittstelle unerwünscht ist, sollten Sie Folgendes verwenden std::function<void(int, int)>: Sie können für diese ein geeignet aufrufbares Funktionsobjekt erstellen, z std::bind().

Die typsicheren Ansätze, bei denen ein Vorlagenargument für den Klassentyp oder ein geeignetes verwendet wird, std::function<...>sind der Verwendung einer void*Schnittstelle vorzuziehen, da sie das Fehlerpotential aufgrund einer Umwandlung in den falschen Typ beseitigen.

Hier ein Beispiel, um zu verdeutlichen, wie ein Funktionszeiger zum Aufrufen einer Elementfunktion verwendet wird:

// 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
quelle
Ok, ich mag diese Antwort! Können Sie bitte angeben, was Sie unter "Rufen Sie Ihr Mitglied über eine Weiterleitungsfunktion auf, die ein Objekt aus der Leere * erhält und dann die Mitgliedsfunktion aufruft" oder einen nützlichen Link dazu freigeben? Vielen Dank
Jorge Leitao
Ich denke ich habe es. (Ich habe Ihren Beitrag bearbeitet) Vielen Dank für die Erklärung und das Beispiel, sehr hilfreich. Nur zur Bestätigung: Für jede Mitgliedsfunktion, auf die ich hinweisen möchte, muss ich eine Weiterleitung erstellen. Richtig?
Jorge Leitao
Na ja, irgendwie. Je nachdem, wie effektiv Sie Vorlagen verwenden, können Sie Weiterleitungsvorlagen erstellen, die mit verschiedenen Klassen und Elementfunktionen arbeiten können. Wie das geht, wäre eine separate Funktion, denke ich ;-)
Dietmar Kühl
1
Ich mag diese Antwort nicht, weil sie verwendet wird void*, was bedeutet, dass Sie sehr böse Fehler bekommen können, weil sie nicht mehr geprüft ist.
Superlokkus
@ Superlokkus: Kannst du uns bitte eine Alternative aufklären?
Dietmar Kühl
88

Die Antwort von @Pete Becker ist in Ordnung, aber Sie können dies auch tun, ohne die classInstanz als expliziten Parameter function1in C ++ 11 zu übergeben:

#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
quelle
1
Ist void function1(std::function<void(int, int)>)richtig?
Deqing
2
Sie müssen dem Funktionsargument einen Variablennamen geben, und dann übergeben Sie den Variablennamen tatsächlich. Also: void function1(std::function<void(int, int)> functionToCall)und dann functionToCall(1,1);. Ich habe versucht, die Antwort zu bearbeiten, aber jemand hat sie aus irgendeinem Grund als nicht sinnvoll abgelehnt. Wir werden sehen, ob es irgendwann positiv bewertet wird.
Dorky Engineer
1
@DorkyEngineer Das ist ziemlich seltsam, ich denke, Sie müssen Recht haben, aber ich weiß nicht, wie dieser Fehler so lange unbemerkt geblieben sein könnte. Wie auch immer, ich habe die Antwort jetzt bearbeitet.
Matt Phillips
2
Ich fand diesen Beitrag, der besagt, dass std :: function einen schwerwiegenden Leistungsverlust verursacht.
Kevin
2
@kevin, vielleicht möchten Sie Ihren Kommentar überarbeiten, da die Antworten dieses Beitrags einen Fehler im Benchmark zeigten.
Jorge Leitao
54

Ein Zeiger auf die Elementfunktion unterscheidet sich von einem Zeiger auf die Funktion. Um eine Elementfunktion über einen Zeiger zu verwenden, benötigen Sie (offensichtlich) einen Zeiger darauf und ein Objekt, auf das Sie sie anwenden können. So ist die entsprechende Version von function1wäre

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

und um es zu nennen:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);
Pete Becker
quelle
1
Vielen Dank. Ich habe gerade herausgefunden, dass Klammern hier zählen: function1 (& (aClass :: test), a) funktioniert mit MSVC2013, aber nicht mit gcc. gcc benötigt das & direkt vor dem Klassennamen (was ich verwirrend finde, da der Operator & die Adresse der Funktion und nicht der Klasse
übernimmt
Ich dachte , functionin void (aClass::*function)(int, int)war eine Art, weil es eine Art ist typedef void (aClass::*function)(int, int).
Olumide
@Olumide - typedef int X;definiert einen Typ; int X;erstellt ein Objekt.
Pete Becker
11

Wenn Sie dies seit 2011 ändern können function1, gehen Sie wie folgt vor:

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

( Live-Demo )

Beachten Sie auch, dass ich Ihre defekte Objektdefinition korrigiert habe ( aClass a();deklariert eine Funktion).

Leichtigkeitsrennen im Orbit
quelle
2

Ich habe eine ähnliche Frage gestellt ( C ++ openframeworks, die von anderen Klassen ungültig sind ), aber die Antwort, die ich gefunden habe, war klarer, daher hier die Erklärung für zukünftige Datensätze:

Es ist einfacher, die std :: -Funktion wie folgt zu verwenden:

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

und dann anrufen als:

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

oder noch einfacher:

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

Hier erstellen Sie ein Lambda, das das Objekt aufruft, das es als Referenz erfasst

Nicola Bertelloni
quelle
1
Angesichts der Tatsache, dass dies mit CPP gekennzeichnet ist, halte ich dies für die sauberste Lösung. Wir könnten jedoch einen konstanten Verweis auf std::functionin verwenden, drawanstatt ihn bei jedem Aufruf zu kopieren.
Sohaib
2
Der Platzhalter sollte in diesem Beispiel nicht verwendet werden, da die Funktion keine Parameter akzeptiert.
Kroiz
1

Ich habe das Mitglied als statisch funktionieren lassen und alles funktioniert:

#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
quelle
Lösen Sie nicht nur die Hauptfrage "Wie kann ich den aClass :: test des a als Argument für function1 verwenden?" Es wird jedoch auch empfohlen, private Klassenvariablen nicht mit Code außerhalb der Klasse zu
ändern
1

Ich bin mir nicht sicher, warum diese unglaublich einfache Lösung verpasst wurde:

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

Ausgabe:

1 - 1 = 0
1 + 1 = 2
hLk
quelle
0

Wenn Sie die Instanz tatsächlich nicht verwenden müssen a (dh Sie können sie wie die Antwort von @mathengineer statisch machen ), können Sie einfach ein Lambda ohne Capture übergeben. (welche zerfallen zum Funktionszeiger)


#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


Hinweis: Wenn aClassder Bau teuer ist oder Nebenwirkungen hat, ist dies möglicherweise kein guter Weg.

Apfel Apfel
quelle
-1

Sie können jetzt aufhören, Ihre Köpfe zu schlagen. Hier ist der Wrapper für die Member-Funktion zur Unterstützung vorhandener Funktionen, die einfache C- Funktionen als Argumente verwenden. thread_localRichtlinie ist hier der Schlüssel.

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

Bitte kommentieren Sie alle Probleme mit diesem Ansatz.

Andere Antworten rufen vorhandene einfache CFunktionen nicht auf: http://cpp.sh/8exun

Necktwi
quelle
3
Anstatt also std :: bind oder ein Lambda zum Umschließen der Instanz zu verwenden, verlassen Sie sich auf eine globale Variable. Ich sehe keinen Vorteil dieses Ansatzes gegenüber den anderen Antworten.
Super
@super, Andere Antworten können vorhandene Funktionen nicht aufrufen und einfache CFunktionen als Argumente verwenden.
Necktwi
2
Die Frage ist, wie man eine Mitgliedsfunktion aufruft. Das Übergeben einer freien Funktion funktioniert in der Frage bereits für OP. Sie geben auch nichts weiter. Hier gibt es nur fest codierte Funktionen und einen globalen Foo_-Zeiger. Wie würde diese Skalierung erfolgen, wenn Sie eine andere Elementfunktion aufrufen möchten? Sie müssten die zugrunde liegenden Funktionen neu schreiben oder für jedes Ziel andere verwenden.
Super