Frage

Ich verwende eine API, die mir einen Funktionszeiger als Rückruf übergeben müssen. Ich versuche, diese API aus meiner Klasse zu verwenden, aber ich bin immer Kompilierungsfehler.

Hier ist, was ich von meinem Konstruktor tat:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Dies gilt nicht kompilieren - bekomme ich folgende Fehlermeldung:

  

Fehler 8 Fehler C3867: 'CLoggersInfra :: RedundencyManagerCallBack': Funktionsaufruf fehlendes Argument Liste; Verwenden Sie '& CLoggersInfra :: RedundencyManagerCallBack' einen Zeiger auf ein Element erstellen

habe ich versucht, den Vorschlag &CLoggersInfra::RedundencyManagerCallBack zu verwenden - nicht für mich zu arbeiten.

Irgendwelche Vorschläge / Erklärung für diese ??

Ich bin mit VS2008.

Danke !!

War es hilfreich?

Lösung

Das funktioniert nicht, weil ein Mitglied Funktionszeiger nicht wie ein normaler Funktionszeiger behandelt werden kann, weil es ein „dieses“ Objekt Argument erwartet.

Sie können stattdessen eine statische Member-Funktion wie folgt über, die wie normale Dritt Funktionen in dieser Hinsicht sind:

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

Die Funktion kann wie folgt definiert werden

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

Andere Tipps

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

Der Compiler ist richtig schlage vor, Sie verwenden & CLoggersInfra :: RedundencyManagerCallBack. Erstens, wenn RedundencyManagerCallBack eine Elementfunktion ist, die Funktion selbst gehört nicht auf eine bestimmte Instanz der Klasse CLoggersInfra. Es gehört zu der Klasse selbst. Wenn Sie jemals eine statische Klasse Funktion vor aufgerufen haben, haben Sie bemerkt, dass Sie die gleiche Someclass :: SomeMemberFunction Syntax. Da die Funktion selbst ‚statisch‘ in dem Sinne, dass es zu der Klasse gehört eher als eine bestimmte Instanz, verwenden Sie die gleiche Syntax. Die ‚&‘ ist notwendig, weil technisch gesehen Sie Funktionen nicht direkt übergeben - Funktionen sind nicht reale Objekte in C ++. Stattdessen vorbei sind Sie technisch die Speicheradresse für die Funktion, das heißt, ein Hinweis darauf, wo die Anweisungen der Funktion im Speicher beginnen. Die Folge ist das gleiche, obwohl, ich ist effektiv ‚eine Funktion übergeben‘ als Parameter.

Aber das ist nur die Hälfte des Problems in diesem Fall. Wie gesagt, ist RedundencyManagerCallBack die Funktion nicht ‚gehört‘ zu einer bestimmten Instanz. Aber es klingt wie Sie es als Rückruf mit einer bestimmten Instanz im Auge passieren wollen. Um zu verstehen, wie diese Sie verstehen müssen tun, was Mitgliederfunktionen wirklich sind. Regelmäßige nicht-definiert-in-jeder-Klasse-Funktionen 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 sind A :: foo nehmen? Normalerweise würden wir sagen 1. Aber unter der Haube, foo nimmt wirklich 2. Blick auf ein :: foo Definition, braucht es eine bestimmte Instanz A, um für die ‚this‘ Zeiger sinnvoll zu sein (der Compiler muss wissen, was ' das ist). Die Art und Weise Sie in der Regel angeben, was Sie wollen, dies 'ist durch die Syntax MyObject.MyMemberFunction zu sein (). Aber das ist nur syntaktischer Zucker die Adresse des MyObject als ersten Parameter zu MyMemberFunction für geben. Ebenso, wenn wir Member-Funktionen innerhalb Klassendefinitionen erklären Sie setzen wir nicht ‚this‘ in der Parameterliste, aber das ist nur ein Geschenk der Sprache Designer Eingabe zu speichern. Stattdessen müssen Sie angeben, dass eine Elementfunktion statisch ist es automatisch zu entscheiden, aus den zusätzlichen ‚this‘ Parameter zu bekommen. Wenn die C ++ Compiler das obige Beispiel in C-Code (die Original C ++ Compiler arbeitete tatsächlich auf diese Weise) übersetzt, wäre es 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);

Rückkehr zu Ihrem Beispiel gibt es jetzt ein offensichtliches Problem. ‚Init‘ will einen Zeiger auf eine Funktion, die einen Parameter annimmt. Aber & CLoggersInfra :: RedundencyManagerCallBack ein Zeiger auf eine Funktion, die zwei Parameter übernimmt, ist es normal, Parameter und das Geheimnis ‚dieser‘ Parameter. So, warum Sie immer noch einen Compiler Fehler. (Als Randnotiz: Wenn Sie jemals Python verwendet haben, diese Art von Verwirrung ist, warum ein ‚Selbst‘ Parameter für alle Elementfunktionen erforderlich)

Die ausführliche Art und Weise zu handhaben ist, ein spezielles Objekt zu erstellen, die einen Zeiger auf die Instanz halten Sie wollen und hat eine Member-Funktion so etwas wie ‚run‘ genannt oder ‚ausführen‘ (oder Überlastungen des ‚()‘ Operator), dass nimmt die Parameter für die Member-Funktion, und ruft einfach die Member-Funktion mit den Parametern auf der gespeicherten Instanz. Aber das müßten Sie ‚Init‘ ändern Ihr spezielles Objekt eher als ein roher Funktionszeiger zu nehmen, und es klingt wie Init jemand anderes Code ist. Und macht eine Sonderklasse für jedes Mal, dieses Problem aufkommt wird Code aufblasen führen.

So, jetzt endlich die gute Lösung, boost :: bind und boost :: Funktion, die Dokumentation für jeden finden Sie hier:

boost :: bind docs boost :: function docs

boost :: bind lassen Sie eine Funktion übernehmen, und einen Parameter zu dieser Funktion und eine neue Funktion machen, wo die Parameter an Ort und Stelle ‚verschlossen‘ ist. Also, wenn ich eine Funktion, die zwei Zahlen addiert, kann ich boost :: bind verwenden, um eine neue Funktion zu machen, wo einer der Parameter gesperrt ist 5. sagen Diese neue Funktion nimmt nur einen Integer-Parameter und werden immer 5 speziell hinzufügen zu. Mit dieser Technik können Sie ‚lock in‘ den verborgenen ‚diesen‘ Parameter eine bestimmte Instanz der Klasse zu sein, und erzeugen eine neue Funktion, die nur einen Parameter, wie Sie wollen (beachten Sie, dass der verborgene Parameter ist immer die erste Parameter, und die normalen Parameter kommen, um danach). Schauen Sie sich die boost :: bind docs für Beispiele, sie sogar speziell diskutieren sie für die Mitgliedsfunktionen. Technisch gesehen ist es eine Standard-Funktion aufgerufen std :: bind1st, die Sie auch nutzen könnten, aber boost :: bind allgemeinere ist.

Natürlich gibt es nur noch einen Haken. boost :: bind wird eine schöne boost :: Funktion für Sie machen, aber das ist immer noch technisch kein raw Funktionszeiger wie Init wahrscheinlich will. Zum Glück bietet boost einen Weg Schub konvertieren :: die Funktion rohe Zeiger, wie auf Stackoverflow dokumentiert

Diese Antwort ist eine Antwort auf einen Kommentar oben und funktioniert nicht mit Visual Studio 2008, soll aber mit neueren Compilern bevorzugt werden.


Zwischenzeit einen void-Zeiger verwenden, Sie müssen nicht mehr und es gibt auch keine Notwendigkeit für Auftrieb, da std::bind und std::function zur Verfügung. Ein Vorteil (im Vergleich zu void-Zeiger) ist die Sicherheit geben, da der Rückgabetypen und die Argumente sind ausdrücklich festgestellt, std::function mit:

// 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 übergeben es an Init:

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

Komplettes Beispiel für die Verwendung von std::bind mit Mitglied, statischen Mitglieder und Nicht-Mitglieder-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!

Darüber hinaus verwenden std::placeholders dynamisch Argumente an den Rückruf passieren kann ( zB dies die Verwendung von return f("MyString"); in Init ermöglicht, wenn f einen String-Parameter hat).

Welches Argument tut Init zu nehmen? Was ist die neue Fehlermeldung?

Methode Zeiger in C ++ sind ein bisschen schwierig zu bedienen. Neben dem Verfahren selbst Zeiger, müssen Sie auch einen Instanz-Zeiger (in Ihrem Fall this) zur Verfügung zu stellen. Init erwartet es als separates Argument vielleicht?

Ein Zeiger auf eine Elementfunktion ist als Zeiger auf eine Funktion, nicht das gleiche. Eine Klasse Mitglied nimmt ein implizites zusätzliches Argument (das das Zeiger), und verwendet eine andere Aufrufkonvention.

Wenn Ihr API eine nonmember Callback-Funktion erwartet, das ist, was Sie es passieren müssen.

Ist m_cRedundencyManager können Member-Funktionen benutzen? Die meisten Rückrufe werden eingerichtet, um reguläre Funktionen oder statische Member-Funktionen zu verwenden. Schauen Sie sich auf dieser Seite bei C ++ FAQ Lite für weitere Informationen.

Update: Die Funktionsdeklaration Sie zeigt zur Verfügung gestellt, die eine Funktion der Form m_cRedundencyManager erwartet: void yourCallbackFunction(int, void *). Member-Funktionen sind daher als Rückrufe in diesem Fall nicht akzeptabel. Eine statische Memberfunktion kann Arbeit, aber wenn die in Ihrem Fall nicht akzeptabel ist, würde der folgende Code auch funktionieren. Beachten Sie, dass es ein Übel aus void * werfen 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);
}

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

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

Dabei gilt CALLBACK_FUNC_EX ist

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

Die Frage und Antwort von der rel="nofollow < a href = "https://isocpp.org/wiki/faq" rel = "nofollow noreferrer"> C ++ FAQ Lite deckt Ihre Frage und die in der Antwort beteiligt Überlegungen ganz gut, denke ich. Kurzer Ausschnitt aus der Web-Seite, die ich verbunden:

  

Sie nicht.

     

Da eine Memberfunktion bedeutungslos ist, ohne dass ein Objekt aufzurufen   es ist, können Sie dies nicht direkt tun (wenn das X-Window-System ist   neu geschrieben in C ++, wäre es wahrscheinlich um Verweise auf Objekte übergeben,   nicht nur Zeiger auf Funktionen; natürlich würde verkörpern die Objekte der   gewünschte Funktion und wahrscheinlich eine ganze Menge mehr).

Necromancing.
Ich denke, die Antworten bisher ein wenig unklar sind.

Nehmen wir ein Beispiel machen:

Angenommen Sie haben eine Reihe von Pixeln (Array von ARGB int8_t Werte)

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

Jetzt wollen Sie eine PNG erzeugen. Dazu rufen Sie die Funktion toJpeg

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

wo writeByte ist eine Callback-Funktion

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

Das Problem hier: FILE * Ausgang hat eine globale Variable sein.
Sehr schlecht, wenn man in einer Multithread-Umgebung ist (beispielsweise ein http-Server).

Sie müssen also einen Weg eine nicht-globale Variable zu machen Ausgabe, während die Callback-Signatur erhalten bleiben.

Die unmittelbare Lösung, die in den Sinn kommt ist ein Verschluss, der wir mit einer Memberfunktion unter Verwendung einer Klasse emulieren kann.

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

Im Gegensatz zu den Erwartungen, das funktioniert nicht.

Der Grund dafür ist, C ++ Member-Funktionen ein bisschen wie Funktionen C # Erweiterung umgesetzt werden.

Sie haben also

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 anrufen mögen, müssen Sie nicht nur die Adresse von writeByte bestehen, sondern auch die Adresse der BadIdea-Instanz.

Wenn Sie also ein typedef für die writeByte Verfahren haben, und es sieht wie folgt aus

typedef void (*WRITE_ONE_BYTE)(unsigned char);

Und Sie haben eine writeJpeg Signatur, die wie folgt aussieht

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

ist es grundsätzlich unmöglich eine zweiAdressenElementFunktion zu ein Ein-Adresse Funktionszeiger zu übergeben (ohne writeJpeg Modifizierung), und es gibt keinen Weg, um es.

Die nächste beste Sache, die Sie in C ++ zu tun, wird mit 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 jedoch Lambda anders tut, ist nichts, als eine Instanz zu einer versteckten Klasse vorbei (wie das „BadIdea“ -Klasse), müssen Sie die Unterschrift von writeJpeg ändern.

Der Vorteil von Lambda über eine manuelle Klasse, ist, dass Sie brauchen nur ein typedef ändern

typedef void (*WRITE_ONE_BYTE)(unsigned char);

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

Und dann kann man alles andere unberührt lassen.

Sie können auch std :: bind

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

Aber das, hinter der Szene, schafft nur eine Lambda-Funktion, die dann muss auch die Änderung in typedef.

Also nein, es gibt keine Möglichkeit, eine Member-Funktion auf ein Verfahren zu übergeben, die einen statischen Funktion-Zeiger erfordert.

Aber lambdas sind der einfache Weg, um vorausgesetzt, dass Sie die Kontrolle über die Quelle.
Sonst bist du kein Glück.
Es gibt nichts, was man mit C ++ tun kann.

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

Da jedoch C ++ können Sie C als auch verwenden, können Sie tun dies mit libffcall in Ebene C, wenn Sie nichts dagegen haben eine Abhängigkeit zu verbinden.

Herunterladen libffcall von GNU (zumindest auf ubuntu, nicht die Distribution bereitgestellte Paket verwenden - es ist gebrochen), entpacken.

./configure
make
make install

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

main.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 Linker-Bibliothek nach add_executable

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

oder Sie könnten nur dynamisch verknüpfen libffcall

target_link_libraries(BitmapLion ffcall)

Hinweis:
Vielleicht möchten Sie die libffcall Header und Bibliotheken schließen, oder ein Cmake Projekt mit dem Inhalt libffcall erstellen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top