wake-up-neo.com

Wie kann ich eine Klassenmitgliedsfunktion als Rückruf übergeben?

Ich verwende eine API, bei der ich einen Funktionszeiger als Rückruf übergeben muss. Ich versuche, diese API aus meiner Klasse zu verwenden, erhalte jedoch Kompilierungsfehler. 

Folgendes habe ich mit meinem Konstruktor gemacht:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Das wird nicht kompiliert - ich erhalte folgende Fehlermeldung: 

Fehler 8 Fehler C3867: 'CLoggersInfra :: RedundencyManagerCallBack': Liste der fehlenden Argumente des Funktionsaufrufs; Verwenden Sie '& CLoggersInfra :: RedundencyManagerCallBack', um einen Zeiger auf das Mitglied zu erstellen

Ich habe den Vorschlag ausprobiert, &CLoggersInfra::RedundencyManagerCallBack zu verwenden - hat bei mir nicht funktioniert. 

Anregungen/Erklärungen dafür?

Ich verwende VS2008.

Vielen Dank!!

55
ofer

Wie Sie nun gezeigt haben, akzeptiert Ihre Init-Funktion eine Nicht-Member-Funktion. also mach es so:

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

und Init mit anrufen

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

Das funktioniert, weil ein Funktionszeiger auf eine statische Elementfunktion kein Elementfunktionszeiger ist und daher wie ein Zeiger auf eine freie Funktion behandelt werden kann.

Dies ist eine einfache Frage, aber die Antwort ist überraschend komplex. Die kurze Antwort ist, dass Sie das tun können, was Sie mit std :: bind1st oder boost :: bind versuchen. Die längere Antwort ist unten.

Der Compiler schlägt vor, dass Sie & CLoggersInfra :: RedundencyManagerCallBack verwenden. Wenn RedundencyManagerCallBack eine Member-Funktion ist, gehört die Funktion selbst nicht zu einer bestimmten Instanz der Klasse CLoggersInfra. Es gehört zur Klasse selbst. Wenn Sie schon einmal eine statische Klassenfunktion aufgerufen haben, haben Sie möglicherweise festgestellt, dass Sie dieselbe SomeClass :: SomeMemberFunction-Syntax verwenden. Da die Funktion selbst "statisch" ist und nicht zu einer bestimmten Instanz gehört, verwenden Sie dieselbe Syntax. Das '&' ist notwendig, weil Sie Funktionen technisch nicht direkt übergeben - Funktionen sind in C++ keine echten Objekte. Stattdessen übergeben Sie technisch die Speicheradresse für die Funktion, d. H. Einen Zeiger darauf, wo die Anweisungen der Funktion im Speicher beginnen. Die Folge ist jedoch die gleiche, dass Sie praktisch eine Funktion als Parameter übergeben.

Aber das ist nur die Hälfte des Problems. Wie gesagt, RedundencyManagerCallBack gehört die Funktion nicht zu einer bestimmten Instanz. Es klingt jedoch so, als ob Sie es als Rückruf mit einer bestimmten Instanz übergeben möchten. Um dies zu verstehen, müssen Sie wissen, welche Elementfunktionen wirklich sind: reguläre, nicht in einer beliebigen Klasse definierte Funktionen mit einem zusätzlichen verborgenen Parameter.

Zum Beispiel:

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!

Wie viele Parameter braucht A :: foo? Normalerweise würde man sagen 1. Aber unter der Haube braucht foo wirklich 2. Wenn man die Definition von A :: foo betrachtet, braucht man eine bestimmte Instanz von A, damit der Zeiger 'this' sinnvoll ist (der Compiler muss wissen, was ' das ist). Die Art und Weise, in der Sie normalerweise angeben, was "dieses" sein soll, ist über die Syntax MyObject.MyMemberFunction (). Dies ist jedoch nur ein syntaktischer Zucker, um die Adresse von MyObject als ersten Parameter an MyMemberFunction zu übergeben. Wenn wir Member-Funktionen innerhalb von Klassendefinitionen deklarieren, fügen wir 'this' nicht in die Parameterliste ein, aber dies ist nur ein Geschenk der Sprachdesigner, um die Eingabe zu speichern. Stattdessen müssen Sie angeben, dass eine Member-Funktion statisch ist, um die automatische Deaktivierung des zusätzlichen Parameters 'this' zu deaktivieren. Wenn der C++ - Compiler das obige Beispiel in C-Code übersetzt (der ursprüngliche C++ - Compiler hat tatsächlich so funktioniert), würde er wahrscheinlich so etwas schreiben:

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

Wenn wir zu Ihrem Beispiel zurückkehren, gibt es jetzt ein offensichtliches Problem. 'Init' möchte einen Zeiger auf eine Funktion, die einen Parameter übernimmt. & CLoggersInfra :: RedundencyManagerCallBack ist jedoch ein Zeiger auf eine Funktion, die zwei Parameter annimmt, den normalen und den geheimen Parameter "this". Deshalb erhalten Sie immer noch einen Compiler-Fehler (als Randbemerkung: Wenn Sie Python verwendet haben, ist diese Art der Verwirrung der Grund dafür, dass für alle Member-Funktionen ein 'self'-Parameter erforderlich ist).

Die ausführlichste Methode, dies zu behandeln, besteht darin, ein spezielles Objekt zu erstellen, das einen Zeiger auf die gewünschte Instanz enthält und über eine Member-Funktion verfügt, die etwa 'run' oder 'execute' heißt (oder den Operator '()' überladen), der die Parameter übernimmt für die Member-Funktion und ruft einfach die Member-Funktion mit diesen Parametern in der gespeicherten Instanz auf. Dies erfordert jedoch, dass Sie 'Init' so ändern müssen, dass Ihr spezielles Objekt anstelle eines reinen Funktionszeigers verwendet wird, und es klingt, als wäre der Code eines anderen Benutzers. Wenn für jedes Problem eine spezielle Klasse erstellt wird, führt dies zum Aufblähen des Codes.

Nun endlich die gute Lösung, die Funktion boost :: bind und boost ::, die Dokumentation für jede hier finden Sie hier:

boost :: bind docs , boost :: function docs

mit boost :: bind können Sie eine Funktion und einen Parameter für diese Funktion übernehmen und eine neue Funktion erstellen, bei der der Parameter an Ort und Stelle 'gesperrt' ist. Wenn ich also eine Funktion habe, die zwei Ganzzahlen hinzufügt, kann ich boost :: bind verwenden, um eine neue Funktion zu erstellen, bei der einer der Parameter gesperrt ist, um 5 zu sagen. Diese neue Funktion nimmt nur einen Ganzzahl-Parameter an und fügt immer spezifisch 5 hinzu dazu Mit dieser Technik können Sie den ausgeblendeten Parameter this als eine bestimmte Klasseninstanz "sperren" und eine neue Funktion generieren, die nur einen Parameter benötigt, so wie Sie möchten (beachten Sie, dass der verborgene Parameter immer der ist -Parameter, und die normalen Parameter kommen in der Reihenfolge danach). In den boost :: bind-Dokumenten finden Sie Beispiele. Sie erörtern sogar die Verwendung für Elementfunktionen. Technisch gesehen gibt es eine Standardfunktion namens std :: bind1st, die Sie ebenfalls verwenden könnten, aber boost :: bind ist allgemeiner.Natürlich gibt es nur noch einen Haken. boost :: bind macht eine Nice boost :: -Funktion für Sie, aber technisch gesehen ist dies kein reiner Funktionszeiger, wie es Init wahrscheinlich wünscht. Glücklicherweise bietet boost eine Möglichkeit, boost :: function in rohe Zeiger umzuwandeln, wie auf StackOverflow hier dokumentiert. Wie dies umgesetzt wird, liegt außerhalb des Rahmens dieser Antwort, obwohl es auch interessant ist.

Machen Sie sich keine Sorgen, wenn dies lächerlich schwer erscheint - Ihre Frage schneidet mehrere dunkle Ecken von C++, und boost :: bind ist unglaublich nützlich, sobald Sie es gelernt haben.

C++ 11-Update: Anstelle von boost :: bind können Sie jetzt eine Lambda-Funktion verwenden, die 'this' erfasst. Dies bedeutet, dass der Compiler dasselbe für Sie generiert.

C++11 update: Instead of boost::bind you can now use a lambda function that captures 'this'. This is basically having the compiler generate the same thing for you.

98
Joseph Garvin

Diese Antwort ist eine Antwort auf einen Kommentar oben und funktioniert nicht mit VisualStudio 2008, sollte jedoch bei neueren Compilern bevorzugt werden.


Inzwischen müssen Sie keinen leeren Zeiger mehr verwenden und es ist auch kein Boost erforderlich, da std::bind und std::function verfügbar sind. One Vorteil (im Vergleich zu ungültigen Zeigern) ist die Typsicherheit, da der Rückgabetyp und die Argumente explizit mit std::function angegeben werden:

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

Dann können Sie den Funktionszeiger mit std::bind erstellen und an Init übergeben:

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

Vollständiges Beispiel für die Verwendung von std::bind mit Member, statischen Member und Nicht-Member-Funktionen:

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

Mögliche Ausgabe:

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

Außerdem können Sie mit std::placeholders dynamisch Argumente an den Rückruf übergeben (z. B. ermöglicht dies die Verwendung von return f("MyString"); in Init, wenn f einen String-Parameter hat).

10
Roi Danton

Welches Argument braucht Init? Was ist die neue Fehlermeldung?

Methodenzeiger in C++ sind etwas schwierig zu verwenden. Neben dem Methodenzeiger selbst müssen Sie auch einen Instanzzeiger angeben (in Ihrem Fall this). Vielleicht erwartet Init es als separates Argument?

3
Konrad Rudolph

Kann m_cRedundencyManager Mitgliedsfunktionen verwenden? Die meisten Callbacks sind so eingerichtet, dass sie reguläre Funktionen oder statische Memberfunktionen verwenden. Werfen Sie einen Blick auf diese Seite unter C++ FAQ Lite, um weitere Informationen zu erhalten.

Aktualisieren: Die von Ihnen bereitgestellte Funktionsdeklaration zeigt, dass m_cRedundencyManager eine Funktion der Form erwartet: void yourCallbackFunction(int, void *). Elementfunktionen sind in diesem Fall daher nicht als Rückrufe zulässig. Eine statische Memberfunktion kann Wenn das in Ihrem Fall nicht akzeptabel ist, würde der folgende Code ebenfalls funktionieren. Beachten Sie, dass es eine böse Besetzung von void * verwendet.


// 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);
}
3
e.James

Ein Zeiger auf eine Klassenmitgliedsfunktion ist nicht mit einem Zeiger auf eine Funktion identisch. Ein Klassenmitglied nimmt ein implizites zusätzliches Argument (den Zeiger this ) an und verwendet eine andere Aufrufkonvention.

Wenn Ihre API eine Nicht-Mitglied-Rückruffunktion erwartet, müssen Sie dies an sie übergeben.

3
jalf

Ich kann sehen, dass die Init die folgende Überschreibung hat: 

Init (CALLBACK_FUNC_EX callback_func, ungültig * callback_parm)

dabei ist CALLBACK_FUNC_EX typedef void (* CALLBACK_FUNC_EX) (int, void *);

1
ofer

Nekromanzierung.
Ich denke, die bisherigen Antworten sind etwas unklar.

Machen wir ein Beispiel:

Angenommen, Sie haben ein Array von Pixeln (Array von ARGB int8_t-Werten)

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

Nun möchten Sie eine PNG generieren. Dazu rufen Sie die Funktion toJpeg auf

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

dabei ist writeByte eine Callback-Funktion

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

Das Problem hier: Die Ausgabe von FILE * muss eine globale Variable sein.
Sehr schlecht, wenn Sie sich in einer Multithread-Umgebung befinden (z. B. einem http-Server).

Sie benötigen also eine Möglichkeit, die Ausgabe einer nicht-globalen Variablen unter Beibehaltung der Rückrufsignatur vorzunehmen.

Die unmittelbare Lösung, die in den Sinn kommt, ist ein Abschluss, den wir mithilfe einer Klasse mit einer Mitgliedsfunktion emulieren können.

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

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

};

Und dann tu es

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

Dies funktioniert jedoch wider Erwarten nicht.

Der Grund dafür ist, dass C++ - Memberfunktionen wie C # -Erweiterungsfunktionen implementiert sind.

Also hast du

class/struct BadIdea
{
    FILE* m_stream;
}

und

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

}

Wenn Sie writeByte aufrufen möchten, müssen Sie nicht nur die Adresse von writeByte übergeben, sondern auch die Adresse der BadIdea-Instanz.

Wenn Sie also eine typedef für die writeByte-Prozedur haben und diese so aussieht

typedef void (*WRITE_ONE_BYTE)(unsigned char);

Und Sie haben eine WriteJpeg-Signatur, die so aussieht

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

es ist grundsätzlich unmöglich, eine Member-Funktion mit zwei Adressen an einen Funktionszeiger mit einer Adresse zu übergeben (ohne writeJpeg zu ändern), und daran führt kein Weg vorbei.

Das nächstbeste, was Sie in C++ tun können, ist die Verwendung einer Lambda-Funktion:

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

Da Lambda jedoch nichts anderes tut, als eine Instanz an eine versteckte Klasse (wie die "BadIdea" -Klasse) zu übergeben, müssen Sie die Signatur von writeJpeg ändern.

Der Vorteil von Lambda gegenüber einer manuellen Klasse besteht darin, dass Sie nur einen Typedef ändern müssen

typedef void (*WRITE_ONE_BYTE)(unsigned char);

zu

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

Und dann können Sie alles andere unberührt lassen.

Sie können auch std :: bind verwenden

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

Dies erzeugt jedoch hinter den Kulissen nur eine Lambda-Funktion, für die dann auch die Änderung von typedef erforderlich ist.

Nein, es gibt keine Möglichkeit, eine Mitgliedsfunktion an eine Methode zu übergeben, die einen statischen Funktionszeiger benötigt.

Aber Lambdas sind der einfache Weg, vorausgesetzt, Sie haben die Kontrolle über die Quelle.
Sonst hast du Pech.
Mit C++ können Sie nichts anfangen.

Hinweis:
std :: function erfordert #include <functional>

Da Sie in C++ jedoch auch C verwenden können, können Sie dies mit libffcall in einfachem C tun, wenn Sie nichts dagegen haben, eine Abhängigkeit zu verknüpfen.

Laden Sie libffcall von GNU herunter (zumindest auf Ubuntu, verwenden Sie nicht das von der Distribution bereitgestellte Paket - es ist kaputt), entpacken Sie es.

./configure
make
make install

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

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

Und wenn Sie CMake verwenden, fügen Sie die Linkbibliothek nach add_executable hinzu

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

oder du könntest einfach libffcall dynamisch verlinken

target_link_libraries(BitmapLion ffcall)

Hinweis:
Möglicherweise möchten Sie die libffcall-Header und -Bibliotheken einschließen oder ein cmake-Projekt mit dem Inhalt von libffcall erstellen.

0
Stefan Steiger

Diese Frage und Antwort aus dem C++ FAQ Lite behandelt Ihre Frage und die Überlegungen, die in der Antwort enthalten sind. Kurzer Auszug aus der von mir verlinkten Webseite:

Nicht.

Weil eine Member-Funktion ohne ein Objekt, das aufgerufen werden soll, bedeutungslos ist Wenn Sie das X Window System in C++ umgeschrieben haben, würde es wahrscheinlich Verweise auf Objekte in der Umgebung übergeben, nicht nur Zeiger auf Funktionen, natürlich würden die Objekte die .__ geforderte Funktion und wahrscheinlich noch viel mehr).

0