Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

domenica 28 aprile 2013

La maledizione della Callback di giada II - Il ritorno
come scrivere una Callback in C - pt.2

Ho deciso di prendermi un attimo di pausa sulla questione del C a oggetti: ho già scritto due post sull'argomento e non voglio annoiarmi né annoiarvi. Lo so, si potrebbero approfondire altri dettagli e parlare (come promesso) di C vs C++, ma, in questo momento, non ne ho voglia. Comunque, prima o poi, ci torneremo di sicuro...

Per cui, oggi, ritorniamo su un argomento già trattato qui, e su cui (mi son reso conto proprio ieri) si potrebbe fare qualche aggiunta interessante. Sto parlando, nuovamente, delle funzioni callback.

Ovviamente, prima di continuare a leggere, dovreste rinfrescarvi la memoria rileggendovi l'altro post sull'argomento, perché questo ne è una estensione, e sono strettamente collegati.

Pausa di riflessione...

Siete già tornati? ma come ? Non avete ancor riletto il vecchio post? Dovete rileggerlo, grazie...

Altra pausa di riflessione...

Ecco, adesso possiamo continuare.

Allora, come avrete ri-notato, l'esempio che avevo proposto era, direi, classico, ispirato all'uso della qsort(), quindi con una callback che si chiama senza argomenti, ma che, in realtà, ne necessita due che verranno generati internamente dalla funzione stessa (la mysort() dell'esempio, o la qsort(), se preferite).

Quindi, riepiloghiamo il flusso dell'esempio: ho scritto un main() che usa la funzione mysort() che, per funzionare, ha bisogno di una funzione di comparazione tra due valori. Ho scritto anche, quindi, la funzione di comparazione (che può essere semplice come quella dell'esempio, ma anche molto più complessa, dipende da quello che si vuole ottenere). La funzione rispettava, ovviamente, il prototipo fornito con la mysort(): ossia, una funzione che ha bisogno di una callback, deve anche dichiarare il prototipo della calback stessa (e se no come la scriviamo?). La mysort() stessa si occupa, poi, di riempire i due parametri della callback con i valori da comparare.

E ora passiamo alla parte nuova: sempre riferendoci al nostro esempio della mysort() (che oramai conoscerete a memoria) supponiamo di avere bisogno di passare un altro parametro alla callback, un parametro esterno disponibile solo a livello della chiamata base, e che la mysort() non può generare internamente.

Come possiamo fare? Vediamo subito il nuovo codice (presentato come un blocco unico, ma, in realtà, da dividere su tre file):
/* parte che dovrebbe essere nel file mysort.h
*/
// prototipi per mySort()
typedef int (*mycallback)(int, int, void *);
void mySort(int *array, int nelems, mycallback cmpFunc, void *fp);

/* parte che dovrebbe essere nel file mysort.c
 */
// mySort() - funzione di sort che usa l'algoritmo bubblesort
void mySort(int *array, int nelems, mycallback cmpFunc, void *fp)
{
    // loop su tutti gli elementi di array
    while (nelems > 0) {
        // loop interno con lunghezza calante
        int i;
        for (i = 0; i < (nelems - 1); i++) {
            // eseguo callback di comparazione
            if (cmpFunc(array[i], array[i + 1], fp)) {
                // eseguo swap di array[i] e array[i+1]
                int temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }

        // decremento nelems
        nelems--;
    }
}

/* parte che dovrebbe essere nel file mymain.c
*/
// cbCmpFunc() - funzione di comparazione
static int cbCmpFunc(int elem_a, int elem_b, void *fp)
{
    // scrivo risultati parziali in un file
    fprintf(fp, "%d > %d = %d\n", elem_a, elem_b, elem_a > elem_b);

    return elem_a > elem_b;
}

// main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);
    FILE *fp = fopen("result.txt", "w");

    // eseguo sort array
    mySort(array, nelems, cbCmpFunc, fp);

    // chiudo file
    fclose(fp);

    // stampo risultati
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);
    printf("\n");

    // esco
    return 0;
}
Come vedete è molto simile all'esempio dell'altro post, solo che ora, a livello del main(), apriamo un file per registrare dei dati di elaborazione e dobbiamo passare il file descriptor alla mysort(), già che non possiamo certo pensare di scrivere una funzione di libreria che porti schiantato nel codice il nome del file di log: è l'applicazione chiamante che deve passarlo.

E come lo passiamo? È abbastanza semplice: si aggiunge un parametro (del tipo opportuno) alla mysort() e alla callback, e, nella chiamata base (nel main(), nel nostro caso) si passa il valore alla mysort(), che si occuperà i propagarlo fino alla callback, che è l'utilizzatrice del nuovo parametro; la mysort() non lo usa, lo trasporta solamente. Con questo metodo possiamo passare tutti i parametri che vogliano: nell'esempio ne ho aggiunto uno, ma se ne possono aggiungere a piacere.

Ovviamente tutto quanto sopra è valido per funzioni che implementiamo noi: non si può certo pensare di aggiungere parametri a funzioni di libreria di cui non abbiamo il controllo: ad esempio la qsort() ha bisogno solo della callback con due parametri, e così ce la dobbiamo tenere.

Qualcuno si chiederà perché il parametro fp è un void* e non un tipo più specifico (in questo caso un FILE*) : l'ho scritto così per dimostrare che, con questo metodo, si può passare qualsiasi cosa (ad esempio in C++ si usa per passare il pointer this): anzi, ho visto codice in cui si aggiungono dei void* alle callback (in fase di progetto) solo per usi futuri, in maniera di poter scrivere, in seguito, delle callback molto personalizzate senza modificare la funzione base (che, come detto, e solo trasportatrice di questi parametri)

Che ve ne sembra? Si, anche questa volta l'argomento suona un po' lapalissiano, ma, se un giorno vi scontrerete con le callback spero vi torni utile. Io, ad esempio, per mancanza di documentazione e altri motivi contingenti, a suo tempo (molto tempo fa) ho dovuto arrangiarmi da solo leggendo codice scritto da altri, e non so cosa avrei dato per avere a disposizione un esempio semplice semplice come quello che vi ho appena proposto (ma, allora, viviamo in un'epoca fortunata... si, ma solo per gli informatici. E neanche tanto. Ma questa è un altra storia...).

Ciao e al prossimo post.