Pagine    Articoli    Prodotti    Forum    Cerca  
Nickname

Password


Non sei registrato?
Registrati a GPI qui!

Puoi anche attivare un vecchio utente GPI e chiedere una nuova password.
I Team

Mappa Team
I nostri utenti

Mappa Utenti
  C++11: Function objects & Lambdas
Pubblicato da Dario Oliveri il 2013-07-12 08:52:11

Qualcuno di voi conoscerà sicuramente il "Function Object pattern". Si tratta in poche parole, di creare un oggetto "eseguibile".

 

Esempio di Function object polimorfico (se sapete già cos'è potete andare avanti)

#include < iostream >
 
/** INTERFACCIA:
*   Definisce un interfaccia che può avere più implementazioni possibili. 
*/
class ICallable{ //in C++ non esistono le "interfacce" come in altri linguaggi,
                 //ma possiamo usare classi "pure abstract" facendo finta che 
         //che siano interfacce
public:
    virtual void doSomething() = 0; // metodo interfaccia da implementare
};
 
/** Funzione che agisce sull'interfaccia (non conosce le implementazioni)*/
void executeCallable(ICallable * p){
    p->doSomething();
}
 
class HelloWorldCallable: public virtual ICallable{
public:
    /** IMPLEMENTA ICallable,
    *   Stampa "Hello World" a schermo. */
    virtual void doSomething() override{
        std::cout < < "Hello World" < < std::endl;
    }
 
    virtual ~HelloWorldCallable(){} //distruttore virtuale, obbligatorio
};
 
class SayGoodbyeCallable: public virtual ICallable{
public:
    /** IMPLEMENTA ICallable,
    *   Stampa "Say Goodbye" a schermo. */
    virtual void doSomething() override{
        std::cout < < "Say Goodbye" < < std::endl;
    }
 
    virtual ~SayGoodbyeCallable(){} //distruttore virtuale, obbligatorio
};
 
int main(){
    //creo 2 oggetti diversi
    SayGoodbyeCallable * goodbye = new SayGoodbyeCallable();
    HelloWorldCallable * hello = new HelloWorldCallable();
 
    // ma grazie all'interfaccia in comune posso usarli entrambi nella stessa funzione.
    executeCallable( goodbye );
    executeCallable( hello );
 
    //elimino ciò che non serve più.
    delete goodbye;
    delete hello;
    return 0;
}

 

Se volete vedere il codice eseguito lo trovate qui:

Esempio di Function Object pattern (è Ideone.com un compilatore C++ online, potrebbe interessarvi anche GCC Godbolt che è in grado di mostrare il "disassembly" anche se non permette di salvare i links come Ideone.com)

 

 

Operatore () 

 

Per rendere più comodo "chiamare gli oggetti funzione" viene normalmente utilizzato l'operatore ()

 

#include < iostream >

//funzione qualunque
int function(){
    return 3;
}
//classe funtore (possiamo usare i suoi oggetti con la stessa sintassi delle funzioni)
class functor{
public:
    int operator() (){ //la prima coppia di parentesi sta solo ad indicare il simbolo con cui chiamiamo questo metodo
        return 2;
    }  
};

int main(){
    functor mioFuntore;
    std::cout << "funzione: " << function() << std::endl; //chiamata di funzione
    std::cout << "funtore: " << mioFuntore() << std::endl; //sembra una chiamata di una funzione.. ma non lo è!
    return 0;
}

 

 

Esempio funtore  (nel link trovate il codice eseguito)

 

Come vedete questo operatore è comodo sintatticamente , usare le parentesi "()" per chiamare un oggetto funzione rende il codice molto più pulito visivamente.

 

Finalmente i Lambda:

Come mostrato dai due esempi precedenti, esitono vari modi di creare "oggetti funzione": sono infatti utilissimi e utilizzatissimi.

 

Il C++ ha introdotto una nuova feature dedicata agli oggetti funzione: I Lambda.

 

A livello di compilatore, i Lambda sono sempre oggetti funzione (classi che utilizzano "operator()"), con la differenza che avendo alcune restrizioni (non possono ereditare da altre classi) vengono ottimizzati meglio.

 

La sintassi è un po strana lo ammetto:

[/*variabili catturate*/] () -> /**returned type*/ { /** Corpo della funzione*/  };

/** TROVO CHE COSI' E' PIU' FACILE DA RICORDARE:
    []()-> type
    {
       return type;
    }

*/

int main(){
    auto Lambda = []()->int {return 0;};
    return Lambda();
}

 

I lambda possono "catturare variabili". Questo è come "passare dei parametri". Ma con una grossa differenza. Nei Lambda i parametri sono passati PRIMA di chiamare la funzione.

 

#include < iostream > 
 
int funzione_normale(int a){
    if(a==0)
        return 0;
    return -a;
}
 
int main(){
    int a=5;
    //creo il primo lambda
    auto Lambda1 = [a] () -> int{ // catturo la variabile per valore
        std::cout << " 1° lambda" << std::endl;
        return funzione_normale(a); 
    };
 
    //creo il secondo lambda
    auto Lambda2 = [&a] () -> int{ // catturo la variabile per reference
        std::cout << " 2° lambda" << std::endl;
        return funzione_normale(a);
    };
 
    if( Lambda1() == funzione_normale(a) )
        std::cout << "sono uguali (primo tentativo)" << std::endl;
 
    a = 3; //cambio a
    if( Lambda1() == funzione_normale(a) )
        std::cout << "sono uguali (secondo tentativo)" << std::endl; //non verrà stampato!
 
    if( Lambda2() == funzione_normale(a) )
        std::cout << "sono uguali (terzo tentativo)" << std::endl; 
 
    return 0;
}

 

Esempio eseguito

 

Catturare variabili per "reference" è molto comodo in quanto ci permette di utilizzarne il valore anche qualora questo valore cambiasse.

 

Come avrete notato ho usato una nuova keyword "auto". E' molto utile nel caso dei lambda, poichè non conosciamo il tipo di un lambda.

 

Tutti i Lambda possono essere salvati in un oggetto "std::function". Il tipo ritornato da una funzione infatti è noto (anche se il tipo del lambda non è noto)

 

#include < functional >
#include < iostream >

int main(){
    float a=3,b=2;
    std::function < float() > multiplyFloat= [a,b]() -> float {return a*b;};

    int c=4,d=5;
    std::function < int() > multiplyInt =  [c,d]() -> int {return c*d;};

    std::cout << multiplyFloat() << " " << multiplyInt() << std::endl;
    return 0;
};

 

Codice eseguito

 

Fine. Mi sono dilungato anche troppo.

 

Campagne crowfunding

Just One Line
Siamo presenti su

     
Copyright ©2016 - Manifesto - Privacy - Termini di Servizio - Community - Collaboratori - Contattaci