2013-09-27 17 views
6

Sto usando std :: bind per fornire un callback mentre astraggo alcune logiche legando prima alcuni parametri. vale a direEmulazione std :: bind in C

void start() { 

    int secret_id = 43534; 

    //Bind the secret_id to the callback function object 
    std::function<void(std::string)> cb = std::bind(&callback, secret_id, std::placeholders::_1); 

    do_action(cb); 

} 

void do_action(std::function<void(std::string)> cb) { 

    std::string result = "hello world"; 
    //Do some things... 

    //Call the callback 
    cb(result); 
} 

void callback(int secret_id, std::string result) { 

    //Callback can now do something with the result and secret_id 

} 

Così, nell'esempio di cui sopra, la do_action non ha bisogno di conoscere la secret_id e altre funzioni in grado di riutilizzarlo senza avere un secret_id propria. Ciò è particolarmente utile quando do_action è una sorta di operazione asincrona.

La mia domanda è, c'è un modo per associare i valori dei parametri ai puntatori di funzione utilizzando solo C?

Se non emulando std :: bind, esiste un altro modo per passare i dati da first() a callback() senza complicare il do_action()?

risposta

12

No. C non consente di farlo direttamente.

In C il modo standard per gestire i callback sta usando puntatori di contesto:

void register_callback(void (*cback)(void *context, int data), 
         void *context); 

questo significa che si passa una funzione che accetta una void * in aggiunta ai normali parametri che la callback dovrebbe gestire (in il caso sopra è un intero) e passerai anche un void * che vuoi essere passato indietro.

Questo void * punti normalmente ad un struct che conterrà tutti i parametri aggiuntivi o dati che vi interessano nella richiamata e utilizzando questo approccio la libreria non dipende da ciò che questo contesto è. Se il callback non ha bisogno di alcun contesto, basta passare un puntatore NULL come context e ignorare il primo parametro quando viene chiamato dalla libreria.

Qualcosa che è una specie di hacker e formalmente non sicuro, ma a volte è fatto è che se il contesto è un dato semplice che si adatta alle dimensioni di un void * (ad esempio, un intero) e se l'ambiente non sta andando ad avere problemi con esso puoi ingannare la libreria passando un falso void * che è solo un numero intero e convertirlo in un numero intero quando viene chiamato dalla libreria (ciò evita al chiamante di allocare il contesto e di gestirne la durata).

su come come per ingannare la lingua per evitare questa limitazione (rimanendo nel paese di portatile C) mi viene in mente qualche trucco:

In primo luogo abbiamo allocare un pool di due argomenti callback e dei dati di contesto

void (*cbf[6])(int, int); 
int ctx[6]; 

allora scriviamo (o macro-generano) funzioni che vogliamo registrare e che chiamerà le versioni due argomenti.

void call_with_0(int x) { cbf[0](ctx[0], x); } 
void call_with_1(int x) { cbf[1](ctx[1], x); } 
void call_with_2(int x) { cbf[2](ctx[2], x); } 
void call_with_3(int x) { cbf[3](ctx[3], x); } 
void call_with_4(int x) { cbf[4](ctx[4], x); } 
void call_with_5(int x) { cbf[5](ctx[5], x); } 

Noi li negozio anche in una piscina dove sono allocate e deallocate:

int first_free_cback = 0; 
int next_free_cback[6] = {1, 2, 3, 4, 5, -1}; 

void (*cbacks[6])(int) = { call_with_0, 
          call_with_1, 
          call_with_2, 
          call_with_3, 
          call_with_4, 
          call_with_5 }; 

Poi di impegnare la primo parametro che possiamo fare qualcosa di simile

void (*bind(void (*g)(int, int), int v0))(int) 
{ 
    if (first_free_cback == -1) return NULL; 
    int i = first_free_cback; 
    first_free_cback = next_free_cback[i]; 
    cbf[i] = g; ctx[i] = v0; 
    return cbacks[i]; 
} 

ma rilegati funzioni deve anche essere esplicitamente deallocato

int deallocate_bound_cback(void (*f)(int)) 
{ 
    for (int i=0; i<6; i++) { 
     if (f == cbacks[i]) { 
      next_free_cback[i] = first_free_cback; 
      first_free_cback = i; 
      return 1; 
     } 
    } 
    return 0; 
} 
3

La risposta breve è no.

L'unica cosa che puoi fare è dichiarare un'altra funzione in cui è incorporato lo secret_id. Se stai usando C99 o più recente puoi renderlo una funzione inline per limitare almeno il sovraccarico della chiamata di funzione, sebbene un compilatore più recente possa farlo da solo comunque.

Per essere sinceri, è tutto ciò che std :: bind sta facendo, poiché sta restituendo una struttura basata su modelli, std :: bind dichiara semplicemente un nuovo functor che ha incorporato secret_id.

4

Come spiegato in 6502, non è possibile farlo in C portatile senza alcun tipo di argomento di contesto passato al callback, anche se non nomina direttamente secret_id. Tuttavia, esistono librerie come Bruno Haible's trampoline che consentono la creazione di funzioni C con informazioni aggiuntive (chiusure) attraverso mezzi non portatili. Queste librerie fanno la loro magia richiamando l'assemblaggio o le estensioni del compilatore, ma sono portati su molte piattaforme popolari; se supportano le architetture di cui si occupa il, funzionano perfettamente.

Tratto da the web, ecco un esempio di codice che trampolino abilita è questa funzione di ordine superiore che accetta parametri a, b e c (analoghi al secret_id, e restituisce una funzione di esattamente un parametro x che calcola a*x^2 + b*x + c :

#include <trampoline.h> 

static struct quadratic_saved_args { 
    double a; 
    double b; 
    double c; 
} *quadratic_saved_args; 

static double quadratic_helper(double x) { 
    double a, b, c; 
    a = quadratic_saved_args->a; 
    b = quadratic_saved_args->b; 
    c = quadratic_saved_args->c; 
    return a*x*x + b*x + c; 
} 

double (*quadratic(double a, double b, double c))(double) { 
    struct quadratic_saved_args *args; 
    args = malloc(sizeof(*args)); 
    args->a = a; 
    args->b = b; 
    args->c = c; 
    return alloc_trampoline(quadratic_helper, &quadratic_saved_args, args); 
} 

int main() { 
    double (*f)(double); 
    f = quadratic(1, -79, 1601); 
    printf("%g\n", f(42)); 
    free(trampoline_data(f)); 
    free_trampoline(f); 
    return 0; 
} 
+0

Le chiusure sono davvero il modo per ottenere questo in "C". LibFFI fornisce chiusure ed è portabile alla maggior parte dei principali sistemi operativi: http://sourceware.org/libffi/ –

+0

@BojanNikolic Buon punto, e libffi è molto più popolare del trampolino. Potrebbe essere apprezzata una risposta con un esempio che risolva il problema dell'OP usando libffi. – user4815162342

3

un tipo opaco e mantenere segreta in una fonte dovrebbero farlo:

#include <stdio.h> 

// Secret.h 

typedef struct TagSecret Secret; 
typedef void (*SecretFunction)(Secret*, const char* visible); 
void secret_call(Secret*, const char* visible); 

// Public.c 

void public_action(Secret* secret, const char* visible) { 
    printf("%s\n", visible); 
    secret_call(secret, visible); 
} 


// Secret.c 

struct TagSecret { 
    int id; 
}; 

void secret_call(Secret* secret, const char* visible) { 
    printf("%i\n", secret->id); 
} 

void start() { 
    Secret secret = { 43534 }; 
    public_action(&secret, "Hello World"); 
} 


int main() { 
    start(); 
    return 0; 
} 

(Quanto sopra non riguarda la registrazione delle funzioni di callback)

Problemi correlati