C++ & Delegate (parte prima)

Thursday, February 05th, 2009 | Author:

Qualche tempo fa il mio amico JP ha pubblicato sul suo blog questo post molto interessante su C++ e delegation. Naturalmente non potevo non dire la mia (e infatti compaio in alcuni miei commenti 😉 ), ma volevo approfondire maggiormente l’argomento 😛 … e cosa c’era di meglio se non vedere cosa offriva lo standard?

Devo ammettere, però, che questo post è uscito più lungo del previsto e rileggerlo per sistemarlo mi sta portando via più tempo del previsto 🙁 . Quindi (anche per non far attendere oltre chi lo stava aspettando… i.e., JP 😛 ) ho deciso di rilasciarlo in due parti. Naturalmente questa è la prima 😉 . A breve pubblicherò anche la seconda, dove verranno mostrare come alcune innovazioni dello standard prossimo venturo siano già alla portata dell’attuale C++ 😉 .

Lo standard attuale…

In realtà c’è ben poco da dire: lo standard attuale non si discosta molto da quello che offre il C (i.e., i puntatori a funzione) e da quello che ha mostrato JP nel suo blog (classi functor).

Approfondiamo il primo caso (per il secondo rimando al blog di JP, che ha già trattato l’argomento in modo approfondito).

Si immagini di avere le seguenti due funzioni

double somma(double a, double b) { return a + b; }
double sottrazione(double a, double b) { return a - b; }

e si immagini di volere avere la possibilità di scegliere a runtime quale funzione utilizzare. Attraverso i puntatori a funzione si può implementare una soluzione di questo tipo:

typedef double (*funzione)(double, double);

funzione F = &somma;
// ...
double risSomma = F(5.0, 4.0); // risSomma <-- 9.0
// ...
F = &sottrazione;
// ...
double risSottrazione = F(5.0, 4.0); // risSottrazione <-- 1.0

A prima vista, sembrerebbe tutto perfetto (e infatti così era in C 😉 ). In realtà, i problemi nascono quando bisogna interagire con i metodi (non statici) di una classe, e in C++ questo è abbastanza facile che succeda 😉 .

Vediamo un esempio:

class media {
public:
    // costruttori, distruttore e quello che serve ...
    virtual double calcolaMedia(double a, double b) {
        return (a + b) / 2.0;
    }

    double calcola(double a, double b) {
        return this->calcolaMedia(a, b);
    }

    double operator()(double a, double b) {
        return this->calcolaMedia(a, b);
    }
};

class media_quadratica : public media {
public:
    // costruttori, distruttore e quello che serve ...
    double calcolaMedia(double a, double b) {
        return sqrt( (a*a + b*b) / 2.0 );
    }
};

In questo caso il typedef necessario a definire il puntatore a funzione (o meglio: il puntatore a metodo) deve essere scritto rendendo esplicito che il metodo appartiene alla classe media:

typedef double (media::*funzione)(double, double);

In oltre bisogna, al momento dell’utilizzo reale del puntatore a funzione, rendere esplicito a quale classe ci si sta riferendo, rendendo di fatto vano l’utilizzo dei puntatori a funzione come delegate (naturalmente tutto “funziona” perché la classe media_quadratica è stata derivata dalla classe media).

funzione F = &media::calcola;
// ...
media M;
media_quadratica MQ;
// ...
double risMedia = (M.*F)(5.0, 15.0); // risMedia <-- 10.0
double risMediaQ = (MQ.*F)(5.0, 15.0); // risMediaQ <-- 11.1803...

In realtà esisterebbe anche un altro metodo… molto più C++ oriented e che fa uso delle funzioni template binder, introdotte con la STL (Standard Template Library):

typedef binder2argfn< mem_fun2_t< double, media, double, double > > funzione;
funzione F = bind2argfn( mem_fun( &media::calcola ), &M );
double risMedia = F(5.0, 15.0); // risMedia <-- 10.0
//...
F = bind2argfn( mem_fun( &media::calcola ), &MQ );
double risMediaQ = F3(5.0, 15.0); // risMediaQ <-- 11.1803...

Utilizzando i binder, perdiamo la dipendenza dalla classe durante l’utilizzo del delegate, ma come contropartita abbiamo una sintassi più complessa sia per definire il delegate stesso che per associargli un metodo. C’è solo un piccolo problema nel codice appena presentato: non funziona. Infatti la STL definisce i binder solo per metodi ad un argomento e il nostro metodo media, invece, ne ha due 🙁 . Per poter compilare ed eseguire il codice appena visto bisogna implementare una serie di binder (e di funzioni correlate ad essi) per supportare i metodi con due argomenti… se poi volessimo supportare metodi con tre argomenti (o quattro, o cinque, …) bisognerebbe implementarsi anche questi binder. Un lavoro non difficile (si può benissimo partire dal codice ufficiale della STL), ma certamente tedioso e poco gratificante: in fin dei conti permette di gestire i delegate solo per i metodi di una classe 😉 .

In questo file binder.zip è possibile trovare una implementazione minimale dei binder per metodi a due argomenti.

…e quello futuro

Cercando una soluzione migliore, sia a livello di usabilità che di stile, mi sono imbattuto in una proposta di qualche anno fa (2002) per il prossimo standard. Questa proposta sembra molto adatta per essere utilizzata per creare dei delegate con una sintassi pulita e leggibile. Naturalmente essendo una proposta, non è detto che sia compatibile al 100% con i compilatori attuali; anche se, in vero, è già stata integrata all’interno delle Boost Library e quindi utilizzabile.

Prima di andare ad analizzarla nel dettaglio ecco un assaggio di come può essere utilizzata per creare dei delegate per le funzioni prima definite:

function < double (double, double) > funzione;

funzione F = &somma;
// ...
double risSomma = F(5, 4); // risSomma <-- 9.0
// ...
F = &sottrazione;
// ...
double risSottrazione = F(5, 4); // risSottrazione <-- 1.0
// ...
media M;
media_quadratica MQ;
// ...
// attenzione: qui in realtà chiamiamo l'operator() definito nella classe media
F = M;
double risMedia = F(5, 15); // risMedia <-- 10.0
// ...
F = MQ;
double risMediaQ = F(5, 15); // risMediaQ <-- 11.1803...

Come si vede la sintassi è molto leggibile e in più è anche abbastanza indipendente dal tipo di funzione (o metodo) che si vuole assegnare al delegate.

Visto che le Boost Library contengono già questa estensione è lecito aspettarsi che sia possibile svilupparla interamente in C++, senza alcuna modifica al compilatore… ma di questo parleremo nella seconda parte 😉 .

That’s all folks 🙂

You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
  1. jp says:

    Dunque dunque… Innanzittutto, direi che come post è grandioso (ma questo te l’avevo già detto in separata sede 😛 ).

    Ovviamente attendo ansioso la prossima parte perchè so (leggi: dovrei sapere 😛 ) cosa conterrà. Ghghgh… 😀

    Alcune note:
    – se tutto procede correttamente sarà possibile disporre di binder “a più argomenti” grazie ai variadic template del prossimo standard.
    – analogamente function sarà resa standard (a dire la verità fa parte del cosiddetto TR1).

    PS: non l’ho mai detto esplicitamente ma mi piace parecchio il modo con cui lo standard C++ si sta evolvendo. Nello specifico, mi piace che molti dei maggiori esperti abbiano dato luogo o partecipino alle Boost, librerie “peer-reviewed”, che stanno diventando la base da cui “estrarre” i nuovi standard. 😀

  2. Eros Pedrini says:

    In realtà non so quanto i variadic template aiuteranno i binder per l’utilizzo qui mostrato (sicuramente saranno la base per avere una function migliore di quelle attualmente disponibili… ma questo lo dico nella seconda parte ;): dico questo perché per come sono stati disegnati i binder adesso il problema è legato anche alle classi “accessorie” che servono per far funzionare il tutto… naturalmente spero di sbagliarmi… confido molto sui variadic template 🙂

    Il TR1, naturalmente lo conosco, e infatti la proposta a cui accenno nel post è alla base di quello che poi è entrato nel TR1 😉 Il problema del TR1 è che non tutti i compilatori “comprendano” queste estensioni…

    Comunque per ora grazie del commento e prometto (ma tengo le dita incrociate per sicurezza) che posterò presto la seconda parte 😛

    cheers

  3. jp says:

    Presto… quando/quanto? 😀

    Quanto all’estensione dei binder con i variadic template, penso soprattutto a forme di currying estremo. Ghghgh… 😀

    Ciau & grazie per il post! ^^

  4. DBarzo says:

    Ciao,
    scusate se mi intrometto un momento..spero di non dire castronerie…ma visto che hai nominato le boost, ci sarebbero anche i signals che possono essere usati per creare degli eventi:

    #include "boost/signals.hpp"
    
    void my_first_slot() {
        std::cout << "void my_first_slot()\n";
    }
    
    class my_second_slot {
    public:
        void operator()() const {
            std::cout << "void my_second_slot::operator()() const\n";
        }
    };
    
    int main() {
        boost::signal sig;
    
        sig.connect(&my_first_slot);
        sig.connect(my_second_slot());
    
        std::cout << "Emitting a signal...\n";
    
        sig();
    }
    

    l’output è:

    Emitting a signal…

    void my_first_slot()

    void my_second_slot::operator()() const

    Chiaramente c’è la possibilità di gestire valori di ritorno e parametri.
    Ciao.

  5. Eros Pedrini says:

    Ciao DBarzo,
    per prima cosa scusa se ho impiegato così tanto tempo ad approvare il tuo commento, ma questa settimana è stata molto “intesa” e quindi non ho avuto tempo di occuparmi del mio blog 🙁 .
    Per seconda, grazie per essere passato di qui 🙂

    Comunque per venire al tuo commento, hai perfettamente ragione e in più si possono usare proprio per fare quanto spiegato da JP nei suoi post: cioè catene di delegate 🙂 La classe function, invece è più “limitata” si occupa solamente di incapsulare il singolo “puntatore a funzione/metodo” (senza possibilità di catene, per intenderci :).

    In realtà, non ho parlato dei signals nel mio post per due motivi: (a) non mi sembra che entreranno nel prossimo standard C++ (ma sono sincero, non sono stato molto attento e quindi potrei sbagliarmi di grosso su questo… se è così correggetemi :); (b) lo scopo di questo post (ma soprattutto del prossimo… che se va tutto bene dovrei finire di correggere la bozza per inizio settimana prossima) era di mostrare come era possibile in C++ realizzare una infrastruttura per il supporto di generici puntatori a funzione/metodo (che poi, si potrebbero estendere per il supporto di catene… e quindi avvicinarsi a quello che fanno signals).

    cheers

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>