«

»

mar 24

il C++ e le lambda ( C++0X – C++11)

Un interessante funzione introdotta nello standard C++0X sono le lambda

Le funzioni lambda sono tipiche dei linguaggi che applicano il paradigma della programmazione funzionale ( completamente o in maniera ibrida) e permettono di trattare le funzioni come se fossero oggetti, potendole quindi definire in qualunque punto del codice e assegnare a una variabile.

Vediamo un esempio di come questo approccio sia stato implementato nel cpp ( se non riesci a compilare il codice vai alla fine dell’articolo)

Il caso più semplice, le funzioni void

#include <iostream>

using namespace std;

auto lambda=[](){
    cout << "Questa e' una lambda" << endl;
};

int main()
{
    auto lambda2=[](){
        cout << "Una lambda puo' essere definita
anche dentro un altra funzione" << endl;
    };

    cout << "Hello world!" << endl;
    lambda();
    lambda2();
    return 0;
}

come si vede dal codice abbiamo definito due funzioni lambda assegnandole a variabili, adesso vediamo come comportarsi per definire funzioni che usano o producono valori (queste qui erano void->void)

Le funzioni che usano e restituiscono parametri

int main()
{
    auto somma=[](int a, int b) {
        return (a+b);
    };

    auto prodotto=[](int a, int b) -> int {
        return (a+b);
    };

    cout << " 2+2 fa " << somma(2,2) << endl;
    cout << " 2*2 fa " << prodotto(2,2) << endl;

    return 0;
}

la lista dei parametri in entrata viene dichiarata tra le parentesi tonde, il tipo del parametro in uscita può essere definito esplicitamente con -> come nel caso di prodotto o lasciato implicito come nel caso di somma e in questo caso viene dedotto dal compilatore

A questo punto vi starete chiedendo a che servano le parentesi quadre suppongo

Le funzioni lambda e la visibilità

La programmazione a oggetti ci ha abituato al concetto di visibilità di una variabile, per esempio una funzione definita dentro una classe accede agli oggetti della classe, ma una lambda che può essere definita in qualunque punto del codice a cosa può accedere? la risposta è: assolutamente a nulla se si vuole fare in modo che qualcosa venga visto dall’interno di una lambda bisogna passare il suo reference tra le quadre, vediamo un esempio

int main()
{
int c;
auto somma=[](int a, int b) {
c=a+b;
};

...

la compilazione del pezzo di codice precedente ci darà un error: ‘c’ is not captured per renderlo visibile dobbiamo fare questo

int main()
{
    int c;
    auto somma=[&c](int a, int b) {
        c=a+b;
    };

    somma(2,2);
    cout << " 2+2 fa " << c << endl;

    return 0;
}

quindi passando un puntatore a this a una lambda rendiamo visibile l’intero oggetto.

è addirittura possibile passare l’intero contesto usando [&] e [=] dove la prima passa per reference e la seconda per valore

Funzioni che ricevono funzioni e funzioni anonime

Una caratteristica fondamentale della programmazione funzionale è la possibilità di passare funzioni ad altre funzioni, in questo caso però la sintassi del cpp è un po sporca e siamo costretti ad usare la libreria functional perché non è interamente possibile farlo direttamente usando solo il linguaggio e le sue keyword, ci servirà la classe generica function<tipouscita (tipiingresso)>

#include <iostream>
#include <fstream>
#include <functional>

using namespace std;

int main()
{
    int c;
    char* filename="dati.txt";
    auto scrivisomma=[&](int a, int b, const function<void (int)>& writer) {
        c=a+b;
        writer(c);
    };

    auto scrittore=[=](int x){
        ofstream myfile;
        myfile.open (filename);
        myfile << x << " \n";
        myfile.close();
    };

    scrivisomma(2,2,scrittore);
    scrivisomma(2,2,[](int x){cout << x << endl;});

    return 0;
}

nell’esempio possiamo notare che è anche possibile definire una funzione e passarla senza assegnarla a una variabile, la funzione resta quindi anonima

Possiamo inoltre vedere come sia possibile modificare il comportamento di una fuzione dal suo esterno passandogli un altra funzione, infatti nel primo caso scrivisomma scrive sullo stdout mentre nel secondo su file

Qualche esempio funzionale ( che funziona)

Adesso cerchiamo di inventarci qualcosa che sia comodo da realizzare con la programmazione funzionale e scomodo con quella procedurale…
Abbiamo visto il caso precedente di una funzione che si aspetta un’altra funzione da utilizzare per scrivere i dati. Un altro uso molto diffuso delle lambda ci permette di modificare il comportamento di un algoritmo passandogli un altro algoritmo, un semplice esempio è una generica funzione di ordinamento a cui passiamo una funzione di confronto, questo ci permette sia di ordinare ogetti di tipi non conosciuti dalla funzione e non ordinabili a priori sia di modificare le relazioni di ordine

#include <iostream>
#include <fstream>
#include <functional>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

using namespace std;

void insertion_sort(void* x[], int n, const function<int (void*,void*)>& comparator) {
  int i, j;
  void* app;

  for (i=1; i<n; i++) {
    app = x[i];
    j = i-1;
    while (j>=0 && comparator(x[j],app)>0) {
      x[j+1] = x[j];
      j--;
    }
    x[j+1] = app;
  }
  return;
}

int main()
{
    srand ( time(0) );
    int c;
    char* cosi[]={"pollo di gomma","ammaccabanane","coso","cannuccia sbirula"};

    auto ordinastr=[](void *a, void *b)->int {
        return strlen((const char*)a)-strlen((const char*)b);
        };

    auto disordinastr=[](void *a, void *b)->int {return rand()%6-3;};

    insertion_sort((void**)cosi,4,ordinastr);
    for(int n=0;n<4;n++)cout << cosi[n] << endl;

    cout<<endl<<endl;

    insertion_sort((void**)cosi,4,disordinastr);
    for(int n=0;n<4;n++)cout << cosi[n] << endl;

    return 0;
}

la prima ordina in base alla lunghezza, la seconda disordina.

(un esempio più interessante sarebbe un thread generico che permetta di eseguire in background una funzione che passiamo come paramentro, ma dei thread ne voglio parlare separatamente)

e per concludere passiamo alle questioni pratiche

L’ambiente utilizzato

le funzioni lambda del C++0X sono incluse nei compilatori GNU a partire dalla 4.5 mentre la maggior parte dei sistemi linux attualmente in circolazione usa la 4.3, per evitare di incasinare il mio linux che è uno di questi ho deciso di installarmi la 4.5 sotto windows con MinGW

Anche lavorando con la 4.5 di default il compilatore non accetta la nuova sintassi, bisogna specificare come flag al g++ -std=c++0x

E per questo articolo è tutto, continuerò la prossima volta parlando del supporto ai thread

E voi lo sapevate che un articolo condiviso su facebook senza un immagine di anteprima viene cliccato molto meno?

chiudo con un immagine parzialmente appropriata per far fare l’anteprima a facebook :D

in ogni caso è una cosa che un programmatore dovrebbe sempre ricordare, il principio del KISS, non complicate le cose se non è necessario!

Questo post fa parte di una serie, gli altri post riguardanti il Cpp0X sono:

Share Button