C++ & Delegate (parte seconda)

Sunday, March 08th, 2009 | Author:

Dopo quasi un mese, (forse) riesco a finire questa seconda parte 😉 Eccoci quindi a vedere se e come è possibile implementare quello che le Boost Library offrono 🙂

Un passo indietro e poi avanti fino alla meta

La prima parte di questo post è possibile leggerla qui: C++ & Delegate (parte prima). Se per caso non l’avete ancora letta la consiglio 😛 almeno per avere una visione completa di questo post.

Come funzionano?

Per avere un idea di come funziona questa estensione è conveniente partire con un problema semplificato. La semplificazione che consideriamo è che vogliamo ottenere un classe function che possa fare da delegate a “qualsiasi” funzione che accetti due parametri di ingresso qualsiasi e un valore di ritorno (che potrebbe essere anche void).

Naturalmente per ottenere questo risultato dovremo utilizzare una classe template, con tre argomenti:

  • R, che indica il tipo di ritorno;
  • A1, che indica il tipo del primo argomento;
  • A2, che indica il tipo del secondo argomento.

Il costruttore di questa classe dovrà anche accettare “qualsiasi” tipo di funzione di ingresso, questo implica che il costruttore stesso dovrà essere parametrizzato via template:

template< typename R, typename A1, typename A2 >
class function {
public:
    template<typename F> function(F &f) {
        // ...
    }

    // ...
};

Questa soluzione presenta però un problema: l’argomento del costruttore deve essere salvato nello stato della classe, ma il parametro template F non può essere spostato a livello di classe perché, altrimenti, andrebbe dichiarato quando si utilizzerà la classe.

In realtà la soluzione sembra semplice: si potrebbe pensare di aggiungere una classe di supporto che dipendenda da tutti e quattro i parametri template (R, A1, A2 e F); in questo modo la funzione di supporto dovrebbe permettere di memorizzare all’interno della classe function la funzione passata come argomento al costruttore. Quindi, un primo tentativo potrebbe essere (si noti che la classe function_helper è una inner class di function in modo tale che che sia legata implicitamente agli argomenti template R, A1 e A2):

template< typename R, typename A1, typename A2 >
class function {
private:
    template< typename F >
    class function_helper {
    public:
        function_helper(const F &f) : MyFunction( const_cast<F&>(f) ) {}

        // ...

    private:
        F &MyFunction;
    };

public:
    template< typename F > function(F &f) {
        this->FunctionHelper = new function_helper<F>(f);
    }

    // ...

private:
    function_helper< ? > *FunctionHelper; // <-- attenzione qui!!!
};

In realtà così facendo abbiamo solamente spostato il problema: infatti la proprietà FunctionHelper dipende dal parametro template F, ma F non è visibile nel punto in cui FunctionHelper viene definita.

La soluzione più semplice è utilizzare una classe base (o un interfaccia) non dipendente dal parametro F come function_helper, in modo tale che possa essere usata come “segnaposto” nella dichiarazione della proprietà:

template< typename R, typename A1, typename A2 >
class function {
private:
    class function_helper_base {
    public:
        virtual ~function_helper_base() {}

        // ...

    };

    template< typename F >
    class function_helper : public function_helper_base {
    public:
        function_helper(const F &f) : MyFunction(  const_cast<F&>(f) ) {}

        // ...

    private:
        F &MyFunction;
    };

public:
    function() : FunctionHelper(NULL) {}

    template< typename F > function(F &f) {
        this->FunctionHelper = new function_helper<F>(f);
    }

    // ...

private:
    function_helper_base *FunctionHelper;
};

Si noti che la proprietà FunctionHelper deve essere necessariamente un puntatore; infatti non è possibile che sia un reference perché non si potrebbe gestire la dichiarazione della classe function senza relativa inizializzazione (i.e., function< int, int, int> Function;).

Per rendere tutto funzionante manca un piccolo sforzo: definire un functor nella classe function in modo tale che sia sintatticamente elegante usare la classe stessa come delegate. Naturalmente questo functor dovrà invocare la funzione memorizzata al suo interno appoggiandosi su function_helper e su function_helper_base:

templatez typename R, typename A1, typename A2 >
class function {
private:
    class function_helper_base {
    public:
        virtual ~function_helper_base() {}

        // ...

        virtual R invoke(A1, A2) const = 0;
    };

    template< typename F >
    class function_helper : public function_helper_base {
    public:
        function_wrapper(const F &f) : MyFunction( const_cast<F&>(f) ) {}

        // ...

        R invoke(A1 a, A2 b) const {
            return ( const_cast<F&>(MyFunction) )(a, b);
        }

    private:
        F &MyFunction;
    };

public:
    template< typename F > function(F &f) {
        this->FunctionHelper = new function_helper<F>(f);
    }

    // ...

    R operator()(A1 a, A2 b) const {
        return FunctionHelper->invoke(a, b);
    }

private:
    function_helper_base *FunctionHelper;
};

Adesso abbiamo in mano una classe che fa (quasi) tutto quello che deve: la classe si comporta infatti come un buon delegate, ma la sua sintassi non è accattivante come quella dell’esempio di partenza:

function < double, double, double > funzione;

Naturalmente con un piccolo sforzo aggiuntivo è possibile anche ottenere la sintassi accattivante 😉 Il segreto sta nel definire una classe template senza corpo con lo stesso nome del nostro delegate: questa classe permetta di gestire la signature (i.e., < double (double, double) >) proprio attraverso il suo argomento template:

template< typename Signature > class function;

template< typename R, typename A1, typename A2 >
class function< R (A1, A2) > {
private:
    class function_helper_base {
    public:
        virtual ~function_helper_base() {}

        // ...

        virtual R invoke(A1, A2) const = 0;
    };

    template< typename F >
    class function_helper : public function_helper_base {
    public:
        function_wrapper(const F &f) : MyFunction(  const_cast<F&>(f) ) {}

        // ...

        R invoke(A1 a, A2 b) const {
            return ( const_cast<F&>(MyFunction) )(a, b);
        }

    private:
        F &MyFunction;
    };

    public:
        template< typename F > function(F &f) {
            this->FunctionHelper = new function_helper<F>(f);
        }

        // ...

        R operator()(A1 a, A2 b) const {
            return FunctionHelper-->invoke(a, b);
        }

private:
        function_helper_base *FunctionHelper;
};

Qui potete trovare l’esempio finale con qualche funzionalità in più (come i costruttori di copia e operatori di assegnamento), ma sempre da considerarsi un mero esempio didattico: Delegate Example.

Che cosa manca?

Ma cosa succede se vogliamo gestire, con il nostro delegate, una funzione con 3 argomenti di ingresso? O senza argomenti? O con un solo argomento?
La risposta (mi spiace 🙁 ) è: bisogna creare tante class function< … > quanti sono i casi che vogliamo gestire. In realtà con la prossima release dello standard la situazione dovrebbe migliorare grazie all’introduzione dei variadic template.

Ma per ora, le opzioni migliori e più utilizzate sono:

  • utilizzare uno script esterno all’ambiente C++ per generare tutti i file sorgenti necessari a coprire i casi (come numero di argomenti di ingresso) che ci interessano. Questa è la soluzione che adottano le Boost Library, dove questo compito è assegnato ad uno script Perl;
  • creare una serie di macro che permettano al preprocessore del C++ di generare i file che servono (a questo scopo segnalo questo articolo di JP: Esempio di XMacro).

Conclusioni e bibliografia

Naturalmente quello che ho mostrato è solamente la base per la creazione di un sistema di gestione dei delegate simile a quello fornito da linguaggi come C#: come minimo bisognerebbe aggiungere la possibilità di concatenare più funzioni in un solo delegate… magari tornerò sull’argomento ;).

Per concludere vi lascio con una serie di link che potrebbero interessarvi se vi è piaciuto questo post 😉 .

  1. Generalized Function Pointers di Herb Sutter;
  2. Yet Another C#-style Delegate Class in Standard C++ di Yingle Jia
  3. A Beginner’s Guide to Pointers di Andrew Peace
  4. Boost Library

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:

    Ottimo! Finalmente! Woot! 😀

    Gli occhi mi si sono incrociati un po’ a seguire le varie inner class, ma penso di aver capito (il che è cosa buona). ^^’

    Grande + grazie! ^^’

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>