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 von meinem Konstruktor gemacht:
m_cRedundencyManager->Init(this->RedundencyManagerCallBack);
Dies wird nicht kompiliert - ich erhalte die 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 versucht, den Vorschlag zu verwenden &CLoggersInfra::RedundencyManagerCallBack
- hat bei mir nicht funktioniert.
Irgendwelche Vorschläge / Erklärungen dafür?
Ich benutze VS2008.
Vielen Dank!!
Dies ist eine einfache Frage, aber die Antwort ist überraschend komplex. Die kurze Antwort lautet: Sie können das tun, was Sie mit
std::bind1st
oder versuchenboost::bind
. Die längere Antwort ist unten.Der Compiler schlägt vor, dass Sie verwenden
&CLoggersInfra::RedundencyManagerCallBack
. Erstens, wennRedundencyManagerCallBack
es sich um eine Mitgliedsfunktion handelt, gehört die Funktion selbst nicht zu einer bestimmten Instanz der KlasseCLoggersInfra
. Es gehört zur Klasse selbst. Wenn Sie schon einmal eine statische Klassenfunktion aufgerufen haben, haben Sie möglicherweise bemerkt, dass Sie dieselbeSomeClass::SomeMemberFunction
Syntax verwenden. Da die Funktion selbst in dem Sinne 'statisch' ist, dass sie eher zur Klasse als zu einer bestimmten Instanz gehört, verwenden Sie dieselbe Syntax. Das '&' ist notwendig, da Sie Funktionen technisch gesehen nicht direkt übergeben - Funktionen sind in C ++ keine realen Objekte. Stattdessen übergeben Sie technisch die Speicheradresse für die Funktion, dh einen Zeiger darauf, wo die Anweisungen der Funktion im Speicher beginnen. Die Konsequenz ist die gleiche, Sie sind effektiv 'Aber das ist in diesem Fall nur das halbe Problem. Wie gesagt,
RedundencyManagerCallBack
die Funktion gehört nicht zu einer bestimmten Instanz. Es hört sich jedoch so an, als ob Sie es als Rückruf für eine bestimmte Instanz übergeben möchten. Um zu verstehen, wie dies zu tun ist, müssen Sie verstehen, was Elementfunktionen wirklich sind: reguläre Funktionen, die in keiner Klasse definiert sind, mit einem zusätzlichen versteckten 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
man? Normalerweise würden wir sagen 1. Aber unter der Haube braucht foo wirklich 2. Wenn man die Definition von A :: foo betrachtet, braucht es eine bestimmte Instanz von A, damit der Zeiger 'this' aussagekräftig ist (der Compiler muss wissen, was '). das ist). Die Art und Weise, wie Sie normalerweise angeben, was "dies" sein soll, erfolgt über die SyntaxMyObject.MyMemberFunction()
. Dies ist jedoch nur syntaktischer Zucker zum Übergeben der Adresse vonMyObject
als erstem Parameter anMyMemberFunction
. Wenn wir Elementfunktionen in Klassendefinitionen deklarieren, fügen wir 'this' nicht in die Parameterliste ein, aber dies ist nur ein Geschenk der Sprachdesigner, um die Eingabe zu sparen. Stattdessen müssen Sie angeben, dass eine Elementfunktion statisch ist, um sie zu deaktivieren und automatisch den zusätzlichen Parameter 'this' zu erhalten. Wenn der C ++ - Compiler das obige Beispiel in C-Code übersetzen würde (der ursprüngliche C ++ - Compiler hat tatsächlich so funktioniert), würde er wahrscheinlich Folgendes 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);
Zurück zu Ihrem Beispiel: Es gibt jetzt ein offensichtliches Problem. 'Init' möchte einen Zeiger auf eine Funktion, die einen Parameter akzeptiert. Ist
&CLoggersInfra::RedundencyManagerCallBack
aber ein Zeiger auf eine Funktion, die zwei Parameter akzeptiert, den normalen Parameter und den geheimen 'this'-Parameter. Aus diesem Grund wird immer noch ein Compilerfehler angezeigt (als Randnotiz: Wenn Sie jemals Python verwendet haben, ist diese Art von Verwirrung der Grund, warum für alle Mitgliedsfunktionen ein 'self'-Parameter erforderlich ist).Die ausführliche Methode, um damit umzugehen, besteht darin, ein spezielles Objekt zu erstellen, das einen Zeiger auf die gewünschte Instanz enthält und über eine Elementfunktion verfügt, die so etwas wie "run" oder "execute" heißt (oder den Operator "()" überlädt), der die Parameter übernimmt für die Member-Funktion und ruft einfach die Member-Funktion mit diesen Parametern in der gespeicherten Instanz auf. Dies würde jedoch erfordern, dass Sie 'Init' ändern, um Ihr spezielles Objekt anstelle eines rohen Funktionszeigers zu verwenden, und es klingt so, als wäre Init der Code eines anderen. Und wenn Sie für jedes Problem eine spezielle Klasse erstellen, wird der Code aufgebläht.
Nun endlich die gute Lösung
boost::bind
undboost::function
die Dokumentation für jede, die Sie hier finden:boost :: bind docs , boost :: function docs
boost::bind
Mit dieser Funktion können Sie eine Funktion und einen Parameter für diese Funktion übernehmen und eine neue Funktion erstellen, bei der dieser Parameter "gesperrt" ist. Wenn ich also eine Funktion habe, die zwei Ganzzahlen hinzufügt, kannboost::bind
ich eine neue Funktion erstellen, bei der einer der Parameter auf 5 gesperrt ist. Diese neue Funktion akzeptiert nur einen Ganzzahlparameter und fügt immer 5 speziell hinzu. Mit dieser Technik können Sie den versteckten Parameter "this" als eine bestimmte Klasseninstanz "sperren" und eine neue Funktion generieren, die nur einen Parameter verwendet, genau wie Sie möchten (beachten Sie, dass der versteckte Parameter immer der erste Parameter ist). und die normalen Parameter kommen in der Reihenfolge danach). Schaue auf dieboost::bind
Dokumente zum Beispiel, sie diskutieren sogar speziell die Verwendung für Mitgliedsfunktionen. Technisch gesehen gibt es eine Standardfunktion[std::bind1st][3]
, die Sie ebenfalls verwenden können, die jedochboost::bind
allgemeiner ist.Natürlich gibt es nur noch einen Haken.
boost::bind
wird eine schöne boost :: function für dich machen, aber dies ist technisch immer noch kein roher Funktionszeiger, wie Init es wahrscheinlich will. Glücklicherweise bietet Boost eine Möglichkeit, Boost :: -Funktionen in Rohzeiger zu konvertieren, wie hier in StackOverflow dokumentiert . Wie es dies umsetzt, geht über den Rahmen dieser Antwort hinaus, obwohl es auch interessant ist.Machen Sie sich keine Sorgen, wenn dies lächerlich schwierig erscheint - Ihre Frage schneidet mehrere der dunkleren Ecken von C ++ und
boost::bind
ist unglaublich nützlich, sobald Sie sie gelernt haben.C ++ 11-Update: Stattdessen
boost::bind
können Sie jetzt eine Lambda-Funktion verwenden, die 'this' erfasst. Dies bedeutet im Grunde, dass der Compiler dasselbe für Sie generiert.quelle
Diese Antwort ist eine Antwort auf einen Kommentar oben und funktioniert nicht mit VisualStudio 2008, sollte jedoch bei neueren Compilern bevorzugt werden.
In der Zwischenzeit müssen Sie keinen Void-Zeiger mehr verwenden und es besteht auch kein Boost mehr, da
std::bind
undstd::function
verfügbar sind. Ein Vorteil (im Vergleich zu ungültigen Zeigern) ist die Typensicherheit, da der Rückgabetyp und die Argumente explizit angegeben werden mitstd::function
:// std::function<return_type(list of argument_type(s))> void Init(std::function<void(void)> f);
Anschließend können Sie den Funktionszeiger mit erstellen
std::bind
und an Init übergeben:auto cLoggersInfraInstance = CLoggersInfra(); auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance); Init(callback);
Vollständiges Beispiel für die Verwendung
std::bind
mit Funktionen für Mitglieder, statische Mitglieder und Nichtmitglieder:#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:
Darüber hinaus können
std::placeholders
Sie Argumente dynamisch an den Rückruf übergeben (z. B. ermöglicht dies die Verwendung vonreturn f("MyString");
in,Init
wenn f einen Zeichenfolgenparameter hat).quelle
Welches Argument braucht
Init
es? Was ist die neue Fehlermeldung?Methodenzeiger in C ++ sind etwas schwierig zu verwenden. Neben dem Methodenzeiger selbst müssen Sie (in Ihrem Fall
this
) auch einen Instanzzeiger angeben . VielleichtInit
erwartet es als separates Argument?quelle
Ein Zeiger auf eine Klassenmitgliedsfunktion ist nicht dasselbe wie ein Zeiger auf eine Funktion. Ein Klassenmitglied verwendet ein implizites zusätzliches Argument (den Zeiger this ) und verwendet eine andere Aufrufkonvention.
Wenn Ihre API eine Rückruffunktion erwartet, die kein Mitglied ist, müssen Sie diese an sie übergeben.
quelle
Können
m_cRedundencyManager
Mitgliedsfunktionen verwendet werden? Die meisten Rückrufe sind so eingerichtet, dass sie reguläre Funktionen oder statische Elementfunktionen verwenden. Weitere Informationen finden Sie auf dieser Seite unter C ++ FAQ Lite.Update: Die von Ihnen angegebene Funktionsdeklaration zeigt,
m_cRedundencyManager
dass eine Funktion des Formulars erwartet wird :void yourCallbackFunction(int, void *)
. Mitgliedsfunktionen sind daher in diesem Fall als Rückrufe nicht akzeptabel. Eine statische Elementfunktion funktioniert möglicherweise , aber wenn dies in Ihrem Fall nicht akzeptabel ist, funktioniert auch der folgende Code. Beachten Sie, dass es eine böse Besetzung von verwendetvoid *
.// 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); }
quelle
Nekromantie.
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];
Jetzt möchten Sie ein PNG generieren. Dazu rufen Sie die Funktion toJpeg auf
bool ok = toJpeg(writeByte, pixels, width, height);
Dabei ist writeByte eine Rückruffunktion
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 zu einer nicht globalen Variablen zu machen, während die Rückrufsignatur beibehalten wird.
Die unmittelbare Lösung, die in den Sinn kommt, ist ein Abschluss, den wir mithilfe einer Klasse mit einer Elementfunktion 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 tun
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 ++ - Mitgliedsfunktionen 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 also writeByte aufrufen möchten, müssen Sie nicht nur die Adresse von writeByte übergeben, sondern auch die Adresse der BadIdea-Instanz.
Wenn Sie also ein typedef für die writeByte-Prozedur haben und es 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 Elementfunktion 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 ein 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, die dann auch die Änderung in typedef benötigt.
Nein, es gibt keine Möglichkeit, eine Elementfunktion an eine Methode zu übergeben, für die ein statischer Funktionszeiger erforderlich ist.
Aber Lambdas sind der einfache Weg, vorausgesetzt, Sie haben die Kontrolle über die Quelle.
Ansonsten hast du kein Glück.
Mit C ++ können Sie nichts anfangen.
Hinweis: Die
Funktion std :: erfordert
#include <functional>
Da Sie in C ++ 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 unter Ubuntu, verwenden Sie nicht das von der Distribution bereitgestellte Paket - es ist kaputt), entpacken Sie es.
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; }
Wenn Sie CMake verwenden, fügen Sie die Linkerbibliothek nach add_executable hinzu
oder Sie können libffcall einfach dynamisch verknüpfen
Hinweis:
Möglicherweise möchten Sie die libffcall-Header und -Bibliotheken einschließen oder ein cmake-Projekt mit dem Inhalt von libffcall erstellen.
quelle
Ich kann sehen, dass der Init die folgende Überschreibung hat:
Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)
wo
CALLBACK_FUNC_EX
isttypedef void (*CALLBACK_FUNC_EX)(int, void *);
quelle
Eine einfache Lösung "Problemumgehung" besteht weiterhin darin, eine Klasse von "Schnittstellen" für virtuelle Funktionen zu erstellen und diese an die Aufruferklasse zu erben. Übergeben Sie es dann als Parameter "könnte im Konstruktor sein" der anderen Klasse, die Sie Ihre Aufruferklasse zurückrufen möchten.
DEFINE-Schnittstelle:
class CallBack { virtual callMeBack () {}; };
Dies ist die Klasse, die Sie zurückrufen möchten:
class AnotherClass () { public void RegisterMe(CallBack *callback) { m_callback = callback; } public void DoSomething () { // DO STUFF // ..... // then call if (m_callback) m_callback->callMeBack(); } private CallBack *m_callback = NULL; };
Und dies ist die Klasse, die zurückgerufen wird.
class Caller : public CallBack { void DoSomthing () { } void callMeBack() { std::cout << "I got your message" << std::endl; } };
quelle
Diese Frage und Antwort aus dem C ++ FAQ Lite behandelt Ihre Frage und die Überlegungen, die mit der Antwort verbunden sind, sehr gut, denke ich. Kurzer Ausschnitt von der Webseite, die ich verlinkt habe:
quelle
Der Zeigertyp auf die nicht statische Elementfunktion unterscheidet sich vom Zeiger auf die normale Funktion .
Typ ist,
void(*)(int)
wenn es sich um eine normale oder statische Elementfunktion handelt.Typ ist,
void(CLoggersInfra::*)(int)
wenn es sich um eine nicht statische Elementfunktion handelt.Sie können also keinen Zeiger an eine nicht statische Elementfunktion übergeben, wenn sie einen normalen Funktionszeiger erwartet.
Darüber hinaus hat eine nicht statische Elementfunktion einen impliziten / versteckten Parameter für das Objekt. Der
this
Zeiger wird implizit als Argument an den Member-Funktionsaufruf übergeben. Daher können die Elementfunktionen nur durch Bereitstellen eines Objekts aufgerufen werden.Wenn die API
Init
nicht geändert werden kann, kann eine Wrapper-Funktion (normale Funktion oder eine statische Elementfunktion der Klasse) verwendet werden, die das Element aufruft. Im schlimmsten Fall wäre das Objekt ein globales Objekt, auf das die Wrapper-Funktion zugreifen kann.CLoggersInfra* pLoggerInfra; RedundencyManagerCallBackWrapper(int val) { pLoggerInfra->RedundencyManagerCallBack(val); }
Wenn die API
Init
kann geändert werden, gibt es viele Alternativen - Objekt nicht-statisches Element Funktionszeiger, Funktionsobjekt,std::function
oder Schnittstellenfunktion.Im Beitrag zu Rückrufen finden Sie Informationen zu den verschiedenen Varianten mit C ++ - Arbeitsbeispielen .
quelle
Sieht so aus, als ob
std::mem_fn
(C ++ 11) genau das tut, was Sie brauchen:quelle