Domanda

Sto utilizzando un'API che mi richiede di passare un puntatore a funzione di callback.Sto cercando di utilizzare questa API dalla mia classe, ma io sono sempre errori di compilazione.

Qui è che cosa ho fatto dal costruttore:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Questo non compilazione ottengo il seguente errore:

Errore 8 errore C3867:'CLoggersInfra::RedundencyManagerCallBack':chiamata di funzione mancanti elenco di argomenti;utilizzare '&CLoggersInfra::RedundencyManagerCallBack' per creare un puntatore a membro

Ho provato il suggerimento di utilizzare &CLoggersInfra::RedundencyManagerCallBack - non ha funzionato per me.

Qualsiasi suggerimento/spiegazione per questo??

Sto usando VS2008.

Grazie!!

È stato utile?

Soluzione

Non funziona perché un puntatore a funzione membro non può essere gestito come un normale puntatore a funzione, poiché prevede un " questo " argomento oggetto.

Invece puoi passare una funzione membro statica come segue, che sono come normali funzioni non membro a questo proposito:

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

La funzione può essere definita come segue

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

Altri suggerimenti

Questa è una domanda semplice ma la risposta è sorprendentemente complessa. La risposta breve è che puoi fare quello che stai cercando di fare con std :: bind1st o boost :: bind. La risposta più lunga è di seguito.

Il compilatore è corretto per suggerire di usare & amp; CLoggersInfra :: RedundencyManagerCallBack. Innanzitutto, se RedundencyManagerCallBack è una funzione membro, la funzione stessa non appartiene a nessuna istanza particolare della classe CLoggersInfra. Appartiene alla classe stessa. Se hai mai chiamato una funzione di classe statica prima, potresti aver notato che usi la stessa sintassi SomeClass :: SomeMemberFunction. Poiché la funzione stessa è "statica", nel senso che appartiene alla classe anziché a una particolare istanza, si utilizza la stessa sintassi. Il '& Amp;' è necessario perché tecnicamente parlando non passi direttamente le funzioni - le funzioni non sono oggetti reali in C ++. Invece stai tecnicamente passando l'indirizzo di memoria per la funzione, ovvero un puntatore a dove iniziano le istruzioni della funzione in memoria. La conseguenza è la stessa però, stai effettivamente "passando una funzione" come parametro.

Ma questo è solo metà del problema in questa istanza. Come ho detto, RedundencyManagerCallBack la funzione non "appartiene" a nessuna istanza particolare. Ma sembra che tu voglia passarlo come callback con in mente un'istanza particolare. Per capire come fare, devi capire quali sono realmente le funzioni membro: normali funzioni non definite in qualsiasi classe con un parametro nascosto extra.

Ad esempio:

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!

Quanti parametri accetta A :: foo? Normalmente diremmo 1. Ma sotto il cofano, foo prende davvero 2. Guardando la definizione di A :: foo, ha bisogno di un'istanza specifica di A affinché il puntatore "this" sia significativo (il compilatore deve sapere cosa " questo è). Il modo in cui di solito si specifica ciò che si desidera 'this' è tramite la sintassi MyObject.MyMemberFunction (). Ma questo è solo zucchero sintattico per passare l'indirizzo di MyObject come primo parametro a MyMemberFunction. Allo stesso modo, quando dichiariamo le funzioni membro all'interno delle definizioni di classe, non inseriamo "this" nell'elenco dei parametri, ma questo è solo un dono dei progettisti del linguaggio per salvare la digitazione. Invece devi specificare che una funzione membro è statica per disattivarla ottenendo automaticamente il parametro 'this' extra. Se il compilatore C ++ traducesse l'esempio sopra in codice C (il compilatore C ++ originale funzionava in quel modo), probabilmente scriverebbe qualcosa del genere:

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

Tornando al tuo esempio, ora c'è un evidente problema. 'Init' vuole un puntatore a una funzione che accetta un parametro. Ma & Amp; CLoggersInfra :: RedundencyManagerCallBack è un puntatore a una funzione che accetta due parametri, è il parametro normale e il parametro segreto "questo". Quindi perché stai ancora ricevendo un errore del compilatore (come nota a margine: se hai mai usato Python, questo tipo di confusione è il motivo per cui è richiesto un parametro 'self' per tutte le funzioni membro).

Il modo dettagliato per gestirlo è creare un oggetto speciale che trattiene un puntatore all'istanza desiderata e abbia una funzione membro chiamata qualcosa come 'run' o 'execute' (o sovraccarica l'operatore '()') che accetta i parametri per la funzione membro e chiama semplicemente la funzione membro con tali parametri sull'istanza memorizzata. Ma questo richiederebbe di cambiare "Init" per prendere l'oggetto speciale anziché un puntatore a funzione non elaborata, e sembra che Init sia il codice di qualcun altro. E creare una classe speciale per ogni volta che si presenta questo problema porterà a un aumento del codice.

Quindi ora, finalmente, la buona soluzione, funzione boost :: bind e boost ::, la documentazione per ognuno che puoi trovare qui:

boost :: bind docs , boost :: function docs

boost :: bind ti permetterà di prendere una funzione e un parametro per quella funzione, e fare una nuova funzione in cui quel parametro è 'bloccato' in posizione. Quindi, se ho una funzione che aggiunge due numeri interi, posso usare boost :: bind per creare una nuova funzione in cui uno dei parametri è bloccato per dire 5. Questa nuova funzione prenderà solo un parametro intero e aggiungerà sempre 5 specificamente ad esso. Usando questa tecnica, puoi 'bloccare' il 'nascosto' questo 'parametro per essere una particolare istanza di classe e generare una nuova funzione che accetta solo un parametro, proprio come vuoi (nota che il parametro nascosto è sempre il primo e i parametri normali vengono ordinati dopo di esso). Guarda i documenti boost :: bind per esempi, discutono anche specificamente di usarli per le funzioni membro. Tecnicamente esiste una funzione standard chiamata std :: bind1st che puoi usare anche, ma boost :: bind è più generale.

Certo, c'è solo un'altra presa. boost :: bind farà una bella funzione boost :: per te, ma questo non è ancora tecnicamente un puntatore a funzioni non elaborate come Init vorrebbe probabilmente. Per fortuna, boost fornisce un modo per convertire boost :: function in puntatori non elaborati, come documentato su StackOverflow qui . Il modo in cui lo implementa va oltre lo scopo di questa risposta, sebbene sia anche interessante.

Non preoccuparti se questo sembra ridicolmente difficile - la tua domanda interseca molti degli angoli più scuri del C ++ e boost :: bind è incredibilmente utile una volta che l'hai imparato.

Aggiornamento C ++ 11: Invece di boost :: bind ora puoi usare una funzione lambda che cattura 'this'. Questo sta fondamentalmente facendo in modo che il compilatore generi la stessa cosa per te.

Questa risposta è una risposta a un commento sopra e non funziona con VisualStudio 2008 ma dovrebbe essere preferita con compilatori più recenti.


Nel frattempo non è più necessario utilizzare un puntatore vuoto e non è inoltre necessario potenziare poiché std::bind e std::function sono disponibili. Uno vantaggio (rispetto ai puntatori vuoti) è la sicurezza del tipo poiché il tipo restituito e gli argomenti sono esplicitamente dichiarati usando std::placeholders:

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

Quindi puoi creare il puntatore a funzione con return f("MyString"); e passarlo a Init:

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

Esempio completo per l'utilizzo di Init con membri, membri statici e funzioni non membro :

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

Possibile output:

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

Inoltre, utilizzando <=> puoi passare dinamicamente argomenti al callback (ad es. questo abilita l'uso di <=> in <=> se f ha un parametro stringa).

Quale argomento accetta Init? Qual è il nuovo messaggio di errore?

I puntatori a metodi in C ++ sono un po 'difficili da usare. Oltre al puntatore del metodo stesso, devi anche fornire un puntatore all'istanza (nel tuo caso this). Forse <=> lo prevede come argomento separato?

Un puntatore a una funzione membro della classe non è uguale a un puntatore a una funzione. Un membro della classe prende un argomento extra implicito (il questo puntatore) e usa una diversa convenzione di chiamata.

Se l'API prevede una funzione di callback non membro, questo è ciò che devi passare ad essa.

m_cRedundencyManager è in grado di utilizzare le funzioni membro? La maggior parte dei callback sono impostati per utilizzare le funzioni regolari o le funzioni dei membri statici. Dai un'occhiata a questa pagina su C ++ FAQ Lite per ulteriori informazioni.

Aggiornamento: La dichiarazione di funzione che hai fornito mostra che void yourCallbackFunction(int, void *) si aspetta una funzione del modulo: void *. Le funzioni membro sono pertanto inaccettabili come richiamate in questo caso. Una funzione membro statica può funzionare, ma se ciò è inaccettabile nel tuo caso, anche il seguente codice funzionerebbe. Nota che utilizza un cast malvagio da <=>.


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

Vedo che init ha la seguente sostituzione:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

dove CALLBACK_FUNC_EX è

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

Questa dalla domanda e risposta dalla < a href = "https://isocpp.org/wiki/faq" rel = "nofollow noreferrer"> C ++ FAQ Lite copre abbastanza bene la tua domanda e le considerazioni relative alla risposta. Breve frammento della pagina Web che ho collegato:

  

Don # 8217 &;. T

     

Perché una funzione membro non ha senso senza un oggetto da invocare   attivo, puoi & # 8217; farlo direttamente (se The X Window System lo fosse   riscritto in C ++, probabilmente passerebbe riferimenti agli oggetti in giro,   non solo puntatori a funzioni; naturalmente gli oggetti avrebbero incarnato il   funzione richiesta e probabilmente molto di più).

Necromancing.
Penso che le risposte date sono un po ' poco chiaro.

Facciamo un esempio:

Suppone che si dispone di una matrice di pixel (array di ARGB int8_t valori)

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

Ora si desidera generare un PNG.Per farlo, si chiama la funzione toJpeg

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

dove writeByte è un callback-funzione

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

Il problema qui:FILE* uscita deve essere una variabile globale.
Molto male se sei in un ambiente multithread (ad es.un server http).

Quindi hai bisogno di qualche modo per rendere l'uscita di una non-variabile globale, pur mantenendo la funzione di callback firma.

La soluzione immediata che le molle in mente è una chiusura, che siamo in grado di emulare l'utilizzo di una classe con una funzione membro.

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

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

};

E poi fare

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

Tuttavia, contrariamente alle aspettative, questo non funziona.

Il motivo è, C++ le funzioni membro sono un pò implementato in C# le funzioni di estensione.

Così si hanno

class/struct BadIdea
{
    FILE* m_stream;
}

e

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

}

Così, quando si desidera chiamare writeByte, è necessario passare non solo l'indirizzo di writeByte, ma anche l'indirizzo del BadIdea istanza.

Così, quando si dispone di un typedef per writeByte procedura, e sembra che questo

typedef void (*WRITE_ONE_BYTE)(unsigned char);

E si dispone di un writeJpeg firma che assomiglia a questo

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

sostanzialmente è impossibile passare una due-indirizzo di una funzione membro a un indirizzo di un puntatore a funzione (senza modificare writeJpeg), e non c'è modo intorno ad esso.

La cosa migliore che si può fare in C++, è l'uso di un lambda-funzione:

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

Tuttavia, a causa lambda sta facendo nulla di diverso, di passare un'istanza di nascosto classe (ad esempio il "BadIdea"-classe), è necessario modificare la firma di writeJpeg.

Il vantaggio di lambda su un manuale classe, è che hai solo bisogno di cambiare un typedef

typedef void (*WRITE_ONE_BYTE)(unsigned char);

per

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

E poi si può lasciare tutto il resto invariato.

Si potrebbe anche usare std::bind

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

Ma questo, dietro la scena, crea solo una funzione lambda, che poi ha anche bisogno di cambiamento typedef.

Quindi no, non c'è modo di passare una funzione membro di un metodo che richiede una funzione statica-puntatore.

Ma lambda sono la soluzione semplice, a condizione che l'utente ha il controllo della sorgente.
In caso contrario, sei fuori di fortuna.
Non c'è niente che si può fare con C++.

Nota:
std::function richiede #include <functional>

Tuttavia, dato che il C++ consente di utilizzare C anche, si può fare questo con libffcall in C, se non ti dispiace collegamento di una dipendenza.

Scarica libffcall da GNU (almeno su ubuntu, non usare la distro fornito pacchetto è rotto), decomprimere.

./configure
make
make install

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

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

E se si utilizza CMake, aggiungere il linker libreria dopo add_executable

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

oppure si può semplicemente collegare dinamicamente libffcall

target_link_libraries(BitmapLion ffcall)

Nota:
Si potrebbe desiderare di includere il libffcall header e le librerie, o creare un cmake progetto con i contenuti di libffcall.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top