Tom, rapito da uno scienziato pazzo, si risveglia legato ad una sedia in una stanza completamente oscura.
Gli viene detto che sul tavolo di fronte a lui ci sono cento monete, dieci di queste mostrano la testa, le altre novanta croce.
Per aver salva la vita deve dividere le cento monete in due gruppi, tale che il numero di teste sia uguale.
Gli viene slegato un braccio, in modo da poter riorganizzare a suo piacimento le monete, e gli viene lasciato solo un minuto di tempo, poi verrà accesa la luce e si verificherà il risultato.
Come procedere?
Inutile pensare di contare sul senso del tatto e cercare di distinguere teste da croci, Tom non ha questa capacità e, in ogni caso, il poco tempo a disposizione lo sconsiglia.
Se si potesse solo muovere le monete, senza girarle, Tom sarebbe probabilmente spacciato. Ma c'è una scappatoia: non gli è stato vietato di cambiare la proporzione tra teste e croci.
Dunque è possibile girare le monete. Il problema è che, data l'oscurità, non è possibile sapere che faccia mostrava prima la moneta che giriamo, e nemmeno che faccia ha assunto.
Tom potrebbe magari pensare a varianti creative (tipo ingoiare tutte e cento le monete e dire di averle partizionate in due gruppi di zero monete, ognuno dei quali ha zero teste) ma difficilmente queste soddisferebbero il suo rapitore.
Le cento monete devono restare sul tavolo e, quando la luce verrà accesa, dovranno essere divise in due gruppi che mostrano lo stesso numero di teste.
Notiamo che non ci viene chiesto di dividere le monete in gruppi uguali, e ci viene da pensare che anche questo sia un indizio interessante.
Proviamo a semplificare il problema: diciamo che sulle cento monete ce ne sia solo una che mostra testa e pensiamo di creare un gruppo contenente una sola moneta, lasciandone 99 dall'altra parte. Consideriamo la nostra moneta singola. Se fosse testa, dall'altra parte devono essere tutte croci; se fosse croce, dall'altra parte c'é una testa. Dunque mi basta girarla per ottenere la soluzione. Se era l'unica testa, adesso è croce, e ho diviso i due gruppi in modo che hanno entrambi zero teste; se era croce, adesso è testa, e i due gruppi hanno entrambi una testa.
Se ci fossero due teste, dividerei la popolazione in due gruppi con 2 e 98 monete, e girerei entrambe le monete del primo gruppo. E così via fino ad arrivare alla soluzione per il problema come enunciato.
In pochi secondi, Tom isola dieci monete e le volta. E, qualunque ora sia il numero di teste sul tavolo, lui salva la propria.
Alan M. Turing
appunti e commenti sulla programmazione in C, C++, Java, SQL
Roulette russa
In genere la roulette russa si fa in due, usando un revolver. Si mette uno o più proiettili nel tamburo, lo si fa girare, si punta l'arma alla propria testa e si tira il grilletto.
Si itera finché qualcuno non trova un proiettile.
Consideriamo un revolver che abbia spazio per 6 proiettili, e per il quale ogni foro del tamburo abbia la stessa probabilità di finire in linea con il grilletto, a prescindere dal numero di proiettili che inseriamo.
Il problema è questo: due persone si affrontano alla roulette, il primo è sopravvissuto e, prima di passare l'arma al secondo, gli chiede se vuole girare il tamburo o preferisce tirare direttamente.
Cosa deve fare il secondo per avere le migliori probabilità di non spararsi?
Si analizzi il caso per un proiettile; due proiettili contigui; due proiettili non contigui.
Si tratta di puro calcolo delle probabilità.
Vediamo il primo caso: un unico proiettile nel tamburo.
Se faccio girare il tamburo, ho una probabilità su sei (0,167 circa) di trovare il proiettile.
Se il mio avversario ha appena superato la prova, ho una informazione aggiuntiva: nella camera di scoppio, quando ha tirato il grilletto, non c'era un proiettile. Dunque ora ho una possibilità di trovare un proiettile su cinque (0,2), e non più sei.
Dunque mi conviene far girare il tamburo.
Secondo caso: due proiettili contigui.
Se faccio girare il tamburo, ho due probabilità su sei (0,333) di trovare un proiettile.
Il mio avversario è ancora vivo, dunque non ha trovato il proiettile. Ma allora posso escludere che il grilletto sia sul secondo dei due proiettili contigui (dato che al mio avversario non è capitato il primo) e su uno dei fori non utilizzati (quello che è capitato al mio avversario). Ho dunque 4 possibili posizioni, di cui una occupata da un proiettile, ovvero una possibilità su quattro (0,25) di trovare un proiettile.
Mi conviene non far girare il tamburo.
Terzo caso: due proiettili non contigui.
Se faccio girare il tamburo, come nel caso precedente, ho due probabilità su sei (0,333) di trovare un proiettile.
Uso l'informazione che nel turno precedente il grilletto non era su uno dei due proiettili. Dunque ora è in una tra le quattro posizioni seguenti ai quattro fori non utilizzati. Due di queste sono occupate dai due proiettili, due sono libere. Due probabilità su quattro (0,5) di finire male.
Perciò faccio girare il tamburo.
Si itera finché qualcuno non trova un proiettile.
Consideriamo un revolver che abbia spazio per 6 proiettili, e per il quale ogni foro del tamburo abbia la stessa probabilità di finire in linea con il grilletto, a prescindere dal numero di proiettili che inseriamo.
Il problema è questo: due persone si affrontano alla roulette, il primo è sopravvissuto e, prima di passare l'arma al secondo, gli chiede se vuole girare il tamburo o preferisce tirare direttamente.
Cosa deve fare il secondo per avere le migliori probabilità di non spararsi?
Si analizzi il caso per un proiettile; due proiettili contigui; due proiettili non contigui.
Si tratta di puro calcolo delle probabilità.
Vediamo il primo caso: un unico proiettile nel tamburo.
Se faccio girare il tamburo, ho una probabilità su sei (0,167 circa) di trovare il proiettile.
Se il mio avversario ha appena superato la prova, ho una informazione aggiuntiva: nella camera di scoppio, quando ha tirato il grilletto, non c'era un proiettile. Dunque ora ho una possibilità di trovare un proiettile su cinque (0,2), e non più sei.
Dunque mi conviene far girare il tamburo.
Secondo caso: due proiettili contigui.
Se faccio girare il tamburo, ho due probabilità su sei (0,333) di trovare un proiettile.
Il mio avversario è ancora vivo, dunque non ha trovato il proiettile. Ma allora posso escludere che il grilletto sia sul secondo dei due proiettili contigui (dato che al mio avversario non è capitato il primo) e su uno dei fori non utilizzati (quello che è capitato al mio avversario). Ho dunque 4 possibili posizioni, di cui una occupata da un proiettile, ovvero una possibilità su quattro (0,25) di trovare un proiettile.
Mi conviene non far girare il tamburo.
Terzo caso: due proiettili non contigui.
Se faccio girare il tamburo, come nel caso precedente, ho due probabilità su sei (0,333) di trovare un proiettile.
Uso l'informazione che nel turno precedente il grilletto non era su uno dei due proiettili. Dunque ora è in una tra le quattro posizioni seguenti ai quattro fori non utilizzati. Due di queste sono occupate dai due proiettili, due sono libere. Due probabilità su quattro (0,5) di finire male.
Perciò faccio girare il tamburo.
Grandi attese per un piccolo incremento
Abbiamo ricevuto l'incarico di scrivere una funzione in C++ che deve rispettare le seguenti specifiche:
input: un vettore di interi positivi
output: un long
Il long in output deve essere generato in modo tale che sia pari al valore più elevato ottenibile come produttoria dei valori passati in input, considerando che possiamo incrementare di una unità uno e un solo elemento dell'array.
Ad esempio, se in input ci viene passato {4,3,2} dobbiamo scegliere se tornare (4+1)*3*2 = 30, 4*(3+1)*2 = 32, o 4*3*(2+1) = 36.
Intermezzo fornito da Belle & Sebastian.
In pratica il requisito può essere tradotto come una richiesta di identificare il minore tra i valori passati, incrementarlo, e ritornare la produttoria. Per autoconvincerci di questo fatto pensiamo ad un caso limite, in cui l'input è composto da {1, 100}. Aggiungendo 1 al primo elemento otteniamo il raddoppio del suo valore, e quindi dell'intera produttoria. Se aggiungessimo 1 al secondo, quel valore crescerebbe solo di un centesimo.
Una prima idea potrebbe essere quella di ordinare in modo crescente il vettore, incrementare il primo elemento, eseguire la produttoria sul vettore modificato:
1. Dato che dobbiamo comunque creare una copia del vettore, tanto vale farla implicitamente nell'intestazione della funzione
2. Con for_each scandiamo l'intero vettore, e la funzione lambda si occupa di mettere il risultato della produttoria nella variabile locale res, che poi torneremo al chiamante.
Non è malaccio, come codice. Soprattutto dà una certa soddisfazione scrivere la riga col for_each e la funzione lambda. Ma lascia un po' l'amaro in bocca la copia e il sorting del vettore, entrambe operazioni non necessarie. Infatti, in realtà, a noi interessa trovare e modificare solo un elemento, tutto il resto potremmo risparmiarcelo.
Invece di sort, usiamo allora min_element(), e poi spezziamo il for_each() in due parti, tenendo l'iteratore all'elemento minore del vettore come separatore tra i due tronconi.
Il codice risultante potrebbe dunque essere questo:
input: un vettore di interi positivi
output: un long
Il long in output deve essere generato in modo tale che sia pari al valore più elevato ottenibile come produttoria dei valori passati in input, considerando che possiamo incrementare di una unità uno e un solo elemento dell'array.
Ad esempio, se in input ci viene passato {4,3,2} dobbiamo scegliere se tornare (4+1)*3*2 = 30, 4*(3+1)*2 = 32, o 4*3*(2+1) = 36.
Intermezzo fornito da Belle & Sebastian.
In pratica il requisito può essere tradotto come una richiesta di identificare il minore tra i valori passati, incrementarlo, e ritornare la produttoria. Per autoconvincerci di questo fatto pensiamo ad un caso limite, in cui l'input è composto da {1, 100}. Aggiungendo 1 al primo elemento otteniamo il raddoppio del suo valore, e quindi dell'intera produttoria. Se aggiungessimo 1 al secondo, quel valore crescerebbe solo di un centesimo.
Una prima idea potrebbe essere quella di ordinare in modo crescente il vettore, incrementare il primo elemento, eseguire la produttoria sul vettore modificato:
#include <vector>
#include <algorithm>
long prodPlus1(vector<int> input) // 1.
{
sort(input.begin(), input.end());
++input[0];
long res = 1;
for_each(input.begin(), input.end(), [&res] (int i) {res *= i;}); // 2.
return res;
}
1. Dato che dobbiamo comunque creare una copia del vettore, tanto vale farla implicitamente nell'intestazione della funzione
2. Con for_each scandiamo l'intero vettore, e la funzione lambda si occupa di mettere il risultato della produttoria nella variabile locale res, che poi torneremo al chiamante.
Non è malaccio, come codice. Soprattutto dà una certa soddisfazione scrivere la riga col for_each e la funzione lambda. Ma lascia un po' l'amaro in bocca la copia e il sorting del vettore, entrambe operazioni non necessarie. Infatti, in realtà, a noi interessa trovare e modificare solo un elemento, tutto il resto potremmo risparmiarcelo.
Invece di sort, usiamo allora min_element(), e poi spezziamo il for_each() in due parti, tenendo l'iteratore all'elemento minore del vettore come separatore tra i due tronconi.
Il codice risultante potrebbe dunque essere questo:
#include <vector>
#include <algorithm>
long prodPlus2(const vector<int>& input)
{
vector<int>::const_iterator it = min_element(input.begin(), input.end());
long res = (*it) + 1;
for_each(input.begin(), it, [&res] (int i) {res *= i;});
for_each(it+1, input.end(), [&res] (int i) {res *= i;});
return res;
}
Singleton
Appunti presi durante la lettura di Modern C++ Design: Generic Programming and Design Patterns Applied, di Andrei Alexandrescu.
Dal capitolo sei: Implementazione dei singleton.
Il pattern Singleton è semplice nella descrizione ma presenta alcune difficoltà non irrilevanti nella sua implementazione.
In pratica un singleton è una variabile globale migliorata, nel senso che non è possibile creare più di un oggetto di quel determinato tipo.
In genere le difficoltà vengono nel gestire il ciclo di vita di un singleton. È relativamente semplice controllarne la creazione, può diventare un incubo controllarne la distruzione.
Il singleton di Meyers
L'idea del singleton di Meyers è quella lasciare al compilatore l'onere di gestire il ciclo di vita dell'oggetto. Per far questo lo si definisce come variabile statica locale alla funzione che ne ritorna l'istanza. In questo modo l'oggetto viene effettivamente creato al momento del primo utilizzo e deallocato al termine dell'esecuzione del programma.
A seguire, un esempio che dovrebbe chiarire l'idea:
Una soluzione semplice ed elegante.
Le cose si complicano se vogliamo un maggiore controllo sul ciclo di vita del singleton, dato che bisogna gestire il problema della possibile morte dell'oggetto quando in realtà ne sarebbe ancora richiesto l'uso da qualche altra componente dell'applicazione. Un altra complicazione nasce dal fatto che il singleton potrebbe far uso di qualche risorsa che potrebbe non essere liberata propriamente al termine dell'applicazione. Il metodo indicato dall'Alexandrescu si basa sull'uso della funzione atexit(), che permette di registrare una funzione per l'esecuzione nella fase terminale del programma.
E una ulteriore complicazione ce la dà la gestione dei singleton in ambiente multithreading. In realtà, se seguiamo l'idea di Meyers possiamo non occuparci del problema, dato che lo deleghiamo al compilatore. Sarà lui a doversi fare carico di gestire possibili condizioni di racing al momento dell'effettiva generazione dell'oggetto. Ma se, per un motivo o per un altro, decidiamo di usare lo schema standard per la creazione di un singleton, mettendo l'oggetto creato nello heap, dobbiamo curare questo passaggio con attenzione.
La soluzione indicata da Alexandrescu è quella del pattern del lock con doppio controllo (double-checked locking pattern), con l'avvertenza di aggiungere un ulteriore controllo per verificare che l'hardware su cui gira la nostra applicazione lo supporti (ci consiglia di fare attenzione soprattutto in caso di ambienti multiprocessore simmetrico che implementino il cosiddetto modello di memoria rilassato).
In pratica usiamo un mutex, un lock, e controlliamo due volte che l'istanza dell'oggetto non sia stata creata. Lo scopo del doppio controllo è quello di evitare di utilizzare il lock appena sia possibile, dato che si tratta del collo di bottiglia prestazionale della vicenda.
Usando boost questa è la parte del codice rilevante:
Dal capitolo sei: Implementazione dei singleton.
Il pattern Singleton è semplice nella descrizione ma presenta alcune difficoltà non irrilevanti nella sua implementazione.
In pratica un singleton è una variabile globale migliorata, nel senso che non è possibile creare più di un oggetto di quel determinato tipo.
In genere le difficoltà vengono nel gestire il ciclo di vita di un singleton. È relativamente semplice controllarne la creazione, può diventare un incubo controllarne la distruzione.
Il singleton di Meyers
L'idea del singleton di Meyers è quella lasciare al compilatore l'onere di gestire il ciclo di vita dell'oggetto. Per far questo lo si definisce come variabile statica locale alla funzione che ne ritorna l'istanza. In questo modo l'oggetto viene effettivamente creato al momento del primo utilizzo e deallocato al termine dell'esecuzione del programma.
A seguire, un esempio che dovrebbe chiarire l'idea:
#include <iostream>
using namespace std;
class MeyersSingleton
{
public:
static MeyersSingleton& getInstance();
void doSomething();
private:
MeyersSingleton();
MeyersSingleton(const MeyersSingleton&); // not implemented
MeyersSingleton& operator=(const MeyersSingleton&); // not implemented
~MeyersSingleton();
};
MeyersSingleton& MeyersSingleton::getInstance()
{
static MeyersSingleton obj;
return obj;
}
MeyersSingleton::MeyersSingleton()
{
cout << "MeyersSingleton ctor" << endl;
}
MeyersSingleton::~MeyersSingleton()
{
cout << "MeyersSingleton dtor" << endl;
}
void MeyersSingleton::doSomething()
{
cout << "MeyersSingleton doSomething" << endl;
}
void tech60() {
cout << "get singleton I" << endl;
MeyersSingleton& ms = MeyersSingleton::getInstance();
ms.doSomething();
cout << "get singleton II" << endl;
MeyersSingleton& ms2 = MeyersSingleton::getInstance();
ms2.doSomething();
cout << "done testing singleton" << endl;
}
Una soluzione semplice ed elegante.
Le cose si complicano se vogliamo un maggiore controllo sul ciclo di vita del singleton, dato che bisogna gestire il problema della possibile morte dell'oggetto quando in realtà ne sarebbe ancora richiesto l'uso da qualche altra componente dell'applicazione. Un altra complicazione nasce dal fatto che il singleton potrebbe far uso di qualche risorsa che potrebbe non essere liberata propriamente al termine dell'applicazione. Il metodo indicato dall'Alexandrescu si basa sull'uso della funzione atexit(), che permette di registrare una funzione per l'esecuzione nella fase terminale del programma.
E una ulteriore complicazione ce la dà la gestione dei singleton in ambiente multithreading. In realtà, se seguiamo l'idea di Meyers possiamo non occuparci del problema, dato che lo deleghiamo al compilatore. Sarà lui a doversi fare carico di gestire possibili condizioni di racing al momento dell'effettiva generazione dell'oggetto. Ma se, per un motivo o per un altro, decidiamo di usare lo schema standard per la creazione di un singleton, mettendo l'oggetto creato nello heap, dobbiamo curare questo passaggio con attenzione.
La soluzione indicata da Alexandrescu è quella del pattern del lock con doppio controllo (double-checked locking pattern), con l'avvertenza di aggiungere un ulteriore controllo per verificare che l'hardware su cui gira la nostra applicazione lo supporti (ci consiglia di fare attenzione soprattutto in caso di ambienti multiprocessore simmetrico che implementino il cosiddetto modello di memoria rilassato).
In pratica usiamo un mutex, un lock, e controlliamo due volte che l'istanza dell'oggetto non sia stata creata. Lo scopo del doppio controllo è quello di evitare di utilizzare il lock appena sia possibile, dato che si tratta del collo di bottiglia prestazionale della vicenda.
Usando boost questa è la parte del codice rilevante:
#include <iostream>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
using namespace std;
namespace {
boost::mutex mtx;
}
class MultithreadedSingleton
{
public:
static MultithreadedSingleton& getInstance();
void doSomething();
private:
MultithreadedSingleton();
MultithreadedSingleton(const MultithreadedSingleton&); // not implemented
MultithreadedSingleton& operator=(const MultithreadedSingleton&); // not implemented
~MultithreadedSingleton();
static MultithreadedSingleton* pInstance_;
};
MultithreadedSingleton* MultithreadedSingleton::pInstance_ = 0;
MultithreadedSingleton& MultithreadedSingleton::getInstance()
{
if (pInstance_ == 0)
{
boost::mutex::scoped_lock lock(mtx);
if (pInstance_ == 0)
{
pInstance_ = new MultithreadedSingleton;
}
}
return *pInstance_;
}
// ...
Static check
Appunti presi durante la lettura di Modern C++ Design: Generic Programming and Design Patterns Applied, di Andrei Alexandrescu.
Dal secondo capitolo: Tecniche
Asserzioni durante la compilazione.
Un problema nell'uso dei template in C++ è come assicurare la correttezza del codice. Diciamo di aver scritto una funzione template che rende più sicura la reinterpret_cast aggiungendo un asserzione:
Ora prima di eseguire una reinterpret_cast si verifica che il tipo in output sia di dimensione tale da non causare una perdita di byte significativi.
E' sicuramente un passo in avanti rispetto a una reinterpret_cast senza controllo ma sarebbe ancora meglio se potessimo fare il controllo in fase di compilazione e non esecuzione.
Per far questo ci viene in aiuto una macro, pensata da Van Horn che funziona bene anche in C usando il fatto che un array di dimensione zero è illegale:
Se l'espressione expr è "vera" la macro genera un array legale, altrimenti uno illegale, e quindi un errore in fase di compilazione.
Riscriviamo la funzione template in questo modo:
Resta il problema della messaggistica generata. Usando STATIC_CHECK, in caso di errore abbiamo una segnalazione sulla dimensione zero dell'array, che non è in realtà molto pertinente con il nostro reale errore. L'Alexandrescu mostra una ingegnosa soluzione che ci permette di generare un messaggio significativo ma che, purtroppo, non è supportato da tutti i compilatori - ad esempio non da quello che sto usando correntemente.
Per cui mi accontento di STATIC_CHECK.
Dal secondo capitolo: Tecniche
Asserzioni durante la compilazione.
Un problema nell'uso dei template in C++ è come assicurare la correttezza del codice. Diciamo di aver scritto una funzione template che rende più sicura la reinterpret_cast aggiungendo un asserzione:
template
To safe_reinterpret_cast(From from)
{
assert(sizeof(From) <= sizeof(To)); return reinterpret_cast(from);
}
Ora prima di eseguire una reinterpret_cast si verifica che il tipo in output sia di dimensione tale da non causare una perdita di byte significativi.
E' sicuramente un passo in avanti rispetto a una reinterpret_cast senza controllo ma sarebbe ancora meglio se potessimo fare il controllo in fase di compilazione e non esecuzione.
Per far questo ci viene in aiuto una macro, pensata da Van Horn che funziona bene anche in C usando il fatto che un array di dimensione zero è illegale:
#define STATIC_CHECK(expr) { char unnamed[(expr) ? 1 : 0]; }
Se l'espressione expr è "vera" la macro genera un array legale, altrimenti uno illegale, e quindi un errore in fase di compilazione.
Riscriviamo la funzione template in questo modo:
template
To checked_reinterpret_cast(From from)
{
STATIC_CHECK(sizeof(From) <= sizeof(To)); return reinterpret_cast(from);
}
Resta il problema della messaggistica generata. Usando STATIC_CHECK, in caso di errore abbiamo una segnalazione sulla dimensione zero dell'array, che non è in realtà molto pertinente con il nostro reale errore. L'Alexandrescu mostra una ingegnosa soluzione che ci permette di generare un messaggio significativo ma che, purtroppo, non è supportato da tutti i compilatori - ad esempio non da quello che sto usando correntemente.
Per cui mi accontento di STATIC_CHECK.
Usare boost::function e boost::bind
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Abbiamo visto che il punto debole di boost::function è proprio nella connessione a funzioni membro e in una certa rigidità nel gestire i parametri alla funzione sottostante. In questo ci viene in aiuto boost::bind.
Vediamo un esempio nell'ambito in cui boost::function entra alla perfezione, la gestione di callback.
Una prima implementazione non fa uso delle librerie boost, ma usa il pattern command per gestire le callback. La classe TapeRecorder mette a disposizione alcune funzionalità che definiscono una sorta di simulatore di registratore vocale. La classe CommandBase definisce l'interfaccia dei comandi che possono essere richiamati dalla GUI, da questa derivano le classi *Command che vengono usate dalla GUI per creare una connessione tra la presentazione e la logica business dell'applicazione:
Il difetto fondamentale di questa implementazione è la sua verbosità. Invece di una classe per ogni comando, potremmo lasciare che la classe command abbia al suo interno un puntatore a funzione generico che, inizializzato dal costruttore, si faccia carico di gestire la connessione verso la corretta funzionalità.
Rivediamo il codice:
Invece di usare un puntatore a funzione, usiamo boost::function, che, in combinazione con boost::bind, ci permette una maggiore elasticità:
Ma ci rendiamo conto che la classe Command2 non è altro che una replica delle funzionalità già offerte da boost::function. Possiamo a questo punto farne a meno, riducendo la definizione della classe ad una typedef:
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Abbiamo visto che il punto debole di boost::function è proprio nella connessione a funzioni membro e in una certa rigidità nel gestire i parametri alla funzione sottostante. In questo ci viene in aiuto boost::bind.
Vediamo un esempio nell'ambito in cui boost::function entra alla perfezione, la gestione di callback.
Una prima implementazione non fa uso delle librerie boost, ma usa il pattern command per gestire le callback. La classe TapeRecorder mette a disposizione alcune funzionalità che definiscono una sorta di simulatore di registratore vocale. La classe CommandBase definisce l'interfaccia dei comandi che possono essere richiamati dalla GUI, da questa derivano le classi *Command che vengono usate dalla GUI per creare una connessione tra la presentazione e la logica business dell'applicazione:
#include <iostream>
using namespace std;
class TapeRecorder {
public:
void play() {
cout << "Since my baby left me..." << endl;
}
void stop() {
cout << "OK, taking a break" << endl;
}
void forward() {
cout << "whizzz" << endl;
}
void rewind() {
cout << "zzzihw" << endl;
}
void record(const string& sound) {
cout << "Recorded: " << sound << endl;
}
};
class CommandBase {
public:
virtual bool enabled() const =0;
virtual void execute() =0;
virtual ~CommandBase() {}
};
class PlayCommand : public CommandBase {
TapeRecorder* p_;
public:
PlayCommand(TapeRecorder* p) : p_(p) {}
bool enabled() const {
return true;
}
void execute() {
p_->play();
}
};
class StopCommand : public CommandBase {
TapeRecorder* p_;
public:
StopCommand(TapeRecorder* p) : p_(p) {}
bool enabled() const {
return true;
}
void execute() {
p_->stop();
}
};
void function05() {
TapeRecorder tr;
cout << "Using the command pattern" << endl;
CommandBase* pPlay= new PlayCommand(&tr);
CommandBase* pStop= new StopCommand(&tr);
cout << "Pressing button play" << endl;
pPlay->execute();
cout << "Pressing button stop" << endl;
pStop->execute();
delete pPlay;
delete pStop;
}
Il difetto fondamentale di questa implementazione è la sua verbosità. Invece di una classe per ogni comando, potremmo lasciare che la classe command abbia al suo interno un puntatore a funzione generico che, inizializzato dal costruttore, si faccia carico di gestire la connessione verso la corretta funzionalità.
Rivediamo il codice:
// ...
class TapeRecorderCommand : public CommandBase {
void (TapeRecorder::*func_)();
TapeRecorder* p_;
public:
TapeRecorderCommand(
TapeRecorder* p,
void (TapeRecorder::*func)()) : p_(p), func_(func) {}
bool enabled() const {
return true;
}
void execute() {
(p_->*func_)();
}
};
void function05() {
TapeRecorder tr;
//...
cout << "Using the improved command" << endl;
pPlay = new TapeRecorderCommand(&tr, &TapeRecorder::play);
pStop = new TapeRecorderCommand(&tr, &TapeRecorder::stop);
cout << "Pressing button play" << endl;
pPlay->execute();
cout << "Pressing button stop" << endl;
pStop->execute();
delete pPlay;
delete pStop;
}
Invece di usare un puntatore a funzione, usiamo boost::function, che, in combinazione con boost::bind, ci permette una maggiore elasticità:
// ...
#include "boost/function.hpp"
#include "boost/bind.hpp"
// ...
class Command2 {
boost::function<void()> f_;
public:
Command2() {}
Command2(boost::function<void()> f) : f_(f) {}
void execute() {
if (f_) {
f_();
}
}
template <typename Func> void setFunction(Func f) {
f_ = f;
}
bool enabled() const {
return f_;
}
};
void function05() {
TapeRecorder tr;
// ...
cout << endl << "Using boost::function and boost::bind" << endl;
Command2 play(boost::bind(&TapeRecorder::play,&tr));
Command2 stop(boost::bind(&TapeRecorder::stop,&tr));
Command2 forward(boost::bind(&TapeRecorder::stop,&tr));
Command2 rewind(boost::bind(&TapeRecorder::rewind,&tr));
Command2 record;
cout << "Pressing button play" << endl;
if (play.enabled()) {
play.execute();
}
cout << "Pressing button stop" << endl;
stop.execute();
cout << "Pressing button record" << endl;
string s = "What a beautiful morning...";
record.setFunction(boost::bind(&TapeRecorder::record, &tr, s));
record.execute();
}
Ma ci rendiamo conto che la classe Command2 non è altro che una replica delle funzionalità già offerte da boost::function. Possiamo a questo punto farne a meno, riducendo la definizione della classe ad una typedef:
typedef boost::function<void()> Command3;
void function05() {
TapeRecorder tr;
// ...
cout << endl << "Fully using boost::function" << endl;
Command3 play3(boost::bind(&TapeRecorder::play, &tr));
Command3 stop3(boost::bind(&TapeRecorder::stop, &tr));
Command3 forward3(boost::bind(&TapeRecorder::stop, &tr));
Command3 rewind3(boost::bind(&TapeRecorder::rewind, &tr));
cout << "Pressing button play" << endl;
play3();
cout << "Pressing button stop" << endl;
stop3();
cout << "Pressing button record" << endl;
string s3 = "What a beautiful morning...";
Command3 record3(boost::bind(&TapeRecorder::record, &tr, s3));
record3();
}
Boost function per functor
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Quando passiamo un functor a boost::function, in realtà ne passiamo una copia. Questo vuol dire che usiamo diverse function queste lavoreranno su differenti copie del functor.
Se vogliamo che il functor sia passato per riferimento a function dobbiamo usare un apposito wrapper di boost, e si ottiene l'effetto chiamando la funzione ref (o cref, se vogliamo una const reference) sull'oggetto.
L'esempio che segue mostra l'uso di default, con i functor copiati, e l'uso con reference, in modo che vi sia in realtà un unico functor condiviso:
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Quando passiamo un functor a boost::function, in realtà ne passiamo una copia. Questo vuol dire che usiamo diverse function queste lavoreranno su differenti copie del functor.
Se vogliamo che il functor sia passato per riferimento a function dobbiamo usare un apposito wrapper di boost, e si ottiene l'effetto chiamando la funzione ref (o cref, se vogliamo una const reference) sull'oggetto.
L'esempio che segue mostra l'uso di default, con i functor copiati, e l'uso con reference, in modo che vi sia in realtà un unico functor condiviso:
#include <iostream>
#include "boost/function.hpp"
using namespace std;
class KeepingState {
int total_;
public:
KeepingState() : total_(0) {}
int operator()(int i) {
total_ += i;
return total_;
}
int total() const {
return total_;
}
};
void function04() {
KeepingState ks;
boost::function<int(int)> f1;
f1 = ks;
boost::function<int(int)> f2;
f2 = ks;
cout << "Default: functor copied" << endl;
cout << "The current total is " << f1(10) << endl;
cout << "The current total is " << f2(10) << endl;
cout << "The total is " << ks.total() << endl;
cout << "Forcing functor by reference" << endl;
f1 = boost::ref(ks);
f2 = boost::ref(ks);
cout << "The current total is " << f1(10) << endl;
cout << "The current total is " << f2(10) << endl;
cout << "The total is " << ks.total() << endl;
}
Boost function per membri di classe
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
É possibile usare boost:function anche per funzioni che sono membro di una classe, anche se l'uso non é così elegante come ci si potrebbe aspettare.
Nell'esempio a seguire vediamo come fare in modo che l'oggetto di riferimento sia passato a function per valore, riferimento o puntatore:
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
É possibile usare boost:function anche per funzioni che sono membro di una classe, anche se l'uso non é così elegante come ci si potrebbe aspettare.
Nell'esempio a seguire vediamo come fare in modo che l'oggetto di riferimento sia passato a function per valore, riferimento o puntatore:
#include <iostream>
#include "boost/function.hpp"
using namespace std;
class AClass {
public:
void doStuff(int i) const {
cout << "Stuff done: " << i << endl;
}
};
void function03() {
cout << "Class function by value" << endl;
boost::function<void(AClass, int)> f1;
f1 = &AClass::doStuff;
f1(AClass(), 1);
cout << "Class function by reference" << endl;
boost::function<void(AClass&, int)> f2;
f2 = &AClass::doStuff;
AClass ac2;
f2(ac2, 2);
cout << "Class function by pointer" << endl;
boost::function<void(AClass*, int)> f3;
f3 = &AClass::doStuff;
AClass ac3;
f3(&ac3, 3);
}
Boost function
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Con boost::function si semplifica la gestione dei puntatori a funzione, permettendo di usare per lo stesso scopo anche functor e qualunque oggetto che si comporti come una funzione.
Callback
Una tipica situazione in cui boost::function ci viene in aiuto, e quando vogliamo gestire dei callback. Nell'esempio che segue confrontiamo l'uso normale, con puntatori a funzione nella classi Notifier, con quello di boost::function, in Notifier2.
A parte la definizione del tipo che parametrizza il vettore, il codice non cambia. Il comportamento é lo stesso, ma usando boost::function ci guadagnamo in genericità. Infatti, oltre a puntatori a funzione, possiamo anche utilizzare functor per fare la nostra callback.
Parte sull'uso di function, nel capitolo undicesimo, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Con boost::function si semplifica la gestione dei puntatori a funzione, permettendo di usare per lo stesso scopo anche functor e qualunque oggetto che si comporti come una funzione.
#include <iostream>
#include "boost/function.hpp"
using namespace std;
bool check(int i, double d) {
return i > d;
}
void function01() {
boost::function<bool (int, double)> f = ✓
if(f(10, 1.1))
cout << "Function works as expected" << endl;
}
Callback
Una tipica situazione in cui boost::function ci viene in aiuto, e quando vogliamo gestire dei callback. Nell'esempio che segue confrontiamo l'uso normale, con puntatori a funzione nella classi Notifier, con quello di boost::function, in Notifier2.
A parte la definizione del tipo che parametrizza il vettore, il codice non cambia. Il comportamento é lo stesso, ma usando boost::function ci guadagnamo in genericità. Infatti, oltre a puntatori a funzione, possiamo anche utilizzare functor per fare la nostra callback.
#include <iostream>
#include <vector>
#include "boost/function.hpp"
using namespace std;
void printNewValue(int i) {
cout << "The value has been updated and is now " << i << endl;
}
void changeObserver(int i) {
cout << "Ah, the value has changed!" << endl;
}
class PrintPreviousValue {
int lastValue_;
public:
PrintPreviousValue() : lastValue_(-1) {}
void operator()(int i) {
cout << "Previous value was " << lastValue_ << endl;
lastValue_ = i;
}
};
class Notifier {
typedef void (*FunType)(int);
vector<FunType> vec_;
int value_;
public:
void addObserver(FunType t) {
vec_.push_back(t);
}
void changeValue(int i) {
value_ = i;
for (size_t i=0; i < vec_.size(); ++i) {
vec_[i](value_);
}
}
};
class Notifier2 {
typedef boost::function<void(int)> FunType;
vector<FunType> vec_;
int value_;
public:
void addObserver(FunType t) {
vec_.push_back(t);
}
void changeValue(int i) {
value_ = i;
for (size_t i = 0; i < vec_.size(); ++i) {
vec_[i](value_);
}
}
};
void function02() {
Notifier n;
n.addObserver(&printNewValue);
n.addObserver(&changeObserver);
n.changeValue(42);
cout << "Using boost::function" << endl;
Notifier2 n2;
n2.addObserver(&printNewValue);
n2.addObserver(&changeObserver);
n2.addObserver(PrintPreviousValue());
n2.changeValue(42);
n2.changeValue(39);
}
Composizione funzionale con boost::bind
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di bind, nel capitolo nove, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Il problema sembra semplice: vogliamo selezionare elementi che siano all'interno di un certo intervallo, tipo (5, 10].
Normalmente risolviamo questa richiesta con codice tipo questo:
Più complicato risulta l'applicazione di questa condizione nel processare un container. L'uso di boost::bind, in connessione con le operazioni della libreria standard C++, ci aiuta ad ottenere una soluzione relativamente semplice.
Dobbiamo in pratica fare un and logico su due confronti, ottenendo questo codice:
Vediamo un esempio che verifica questo costrutto. Mettiamo in un vettore alcuni valori, poi usiamo count_if per contare quanti elementi del vettore rispettano la nostra condizione e find_if per trovare il primo elemento che le rispetta:
Un altro possibile uso é quello di applicare diverse operazioni su ogni singolo elemento di un container.
Nell'esempio che segue vogliamo aggiungere 10 a tutti gli elementi e quindi togliere il 5% dal valore risultante. Abbiamo quindi un binding composto che, notiamo, parte dall'operazione più interna.
Parte sull'uso di bind, nel capitolo nove, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Il problema sembra semplice: vogliamo selezionare elementi che siano all'interno di un certo intervallo, tipo (5, 10].
Normalmente risolviamo questa richiesta con codice tipo questo:
if(i>5 && i<=10) {
// ...
}
Più complicato risulta l'applicazione di questa condizione nel processare un container. L'uso di boost::bind, in connessione con le operazioni della libreria standard C++, ci aiuta ad ottenere una soluzione relativamente semplice.
Dobbiamo in pratica fare un and logico su due confronti, ottenendo questo codice:
boost::bind(std::logical_and(),
boost::bind(std::greater(), _1, 5),
boost::bind(std::less_equal(), _1, 10));
Vediamo un esempio che verifica questo costrutto. Mettiamo in un vettore alcuni valori, poi usiamo count_if per contare quanti elementi del vettore rispettano la nostra condizione e find_if per trovare il primo elemento che le rispetta:
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include "boost/bind.hpp"
using namespace std;
void bind04() {
vector<int> v;
v.push_back(7);
v.push_back(4);
v.push_back(12);
v.push_back(10);
int count = count_if(v.begin(), v.end(),
boost::bind(logical_and<bool>(),
boost::bind(greater<int>(), _1, 5),
boost::bind(less_equal<int>(), _1, 10)));
cout << "Found " << count << " items" << endl;
vector<int>::iterator it = find_if(v.begin(), v.end(),
boost::bind(logical_and<bool>(),
boost::bind(greater<int>(),_1,5),
boost::bind(less_equal<int>(),_1,10)));
if (it != v.end()) {
cout << "First item: " << *it << endl;
}
}
Un altro possibile uso é quello di applicare diverse operazioni su ogni singolo elemento di un container.
Nell'esempio che segue vogliamo aggiungere 10 a tutti gli elementi e quindi togliere il 5% dal valore risultante. Abbiamo quindi un binding composto che, notiamo, parte dall'operazione più interna.
#include <iostream>
#include <list>
#include <functional>
#include <algorithm>
#include "boost/bind.hpp"
using namespace std;
void bind05() {
list<double> values;
values.push_back(10.0);
values.push_back(100.0);
values.push_back(1000.0);
transform(values.begin(), values.end(), values.begin(),
boost::bind(multiplies<double>(), 0.95,
boost::bind(plus<double>(), _1, 10)));
copy(values.begin(), values.end(), ostream_iterator<double>(cout," "));
cout << endl;
}
Criteri di ordinamento dinamico via Boost bind
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di bind, nel capitolo nove, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Un utilizzo di boost::bind é in connessione con l'uso di sort su di un container. Infatti permette di creare sul posto una regola per l'ordinamento del container.
Nell'esempio che segue vediamo come ordinare un vettore di oggetti di una classe che non é fornita di operatori di ordinamento:
La prima sort viene eseguita usando la funzione age() e l'operatore less, il secondo con surname().
Parte sull'uso di bind, nel capitolo nove, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
Un utilizzo di boost::bind é in connessione con l'uso di sort su di un container. Infatti permette di creare sul posto una regola per l'ordinamento del container.
Nell'esempio che segue vediamo come ordinare un vettore di oggetti di una classe che non é fornita di operatori di ordinamento:
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
#include "boost/bind.hpp"
using namespace std;
class PersonalInfo {
string name_;
string surname_;
unsigned int age_;
public:
PersonalInfo(const string& n, const string& s, unsigned int age) :
name_(n), surname_(s), age_(age) {}
string name() const {
return name_;
}
string surname() const {
return surname_;
}
unsigned int age() const {
return age_;
}
};
ostream& operator<< (ostream& os, const PersonalInfo& pi) {
os << pi.name() << ' ' << pi.surname() << ' '
<< pi.age() << endl;
return os;
}
void dump(vector<PersonalInfo>& vec) {
vector<PersonalInfo>::const_iterator it = vec.begin();
while(it != vec.end()) {
cout << *it++;
}
cout << endl;
}
void bind03() {
vector<PersonalInfo> vec;
vec.push_back(PersonalInfo("Little", "John", 30));
vec.push_back(PersonalInfo("Friar", "Tuck", 50));
vec.push_back(PersonalInfo("Robin", "Hood", 40));
dump(vec);
cout << "By age:" << endl;
sort(vec.begin(), vec.end(), boost::bind(
less<unsigned int>(),
boost::bind(&PersonalInfo::age, _1),
boost::bind(&PersonalInfo::age, _2)));
dump(vec);
cout << "By surname:" << endl;
sort(vec.begin(), vec.end(), boost::bind(
less<string>(),
boost::bind(&PersonalInfo::surname, _1),
boost::bind(&PersonalInfo::surname, _2)));
dump(vec);
}
La prima sort viene eseguita usando la funzione age() e l'operatore less, il secondo con surname().
Ancora su boost::bind
Rileggendo Beyond the C++ Standard Library: An Introduction to Boost di Björn Karlsson.
Parte sull'uso di bind, nel capitolo nove, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
L'uso di boost::bind é estremamente flessibile. Nell'esempio che segue si vede come il confronto con std::mem_fun_ref la veda vincente.
Il bello di boost::bind é che funziona quando il parametro passato, identificato con il segnaposto _1, può essere un valore, un puntatore, o anche uno smart pointer. Usando le funzionalità offerte dalla libreria standard (in attesa della riscrittura 0x) occorre usare l'adattatore mem_fun_ref, mem_fun, o disperarsi per l'assenza di disponibilità per smart pointer.
Nell'esempio seguente vediamo come boost::bind lavori indifferentemente con funzioni libere o membri di classi. Bisogna solo fare attenzione ai parametri passati via segnaposto. Nel caso di funzioni membro non statiche, che richiedano quindi un "this" su cui operare, il primo segnaposto deve essere dedicato a un oggetto della classe a cui fare riferimento.
Nel primo esempio boost::bind invoca una funzione libera, passandole via il primo segnaposto, _1, una stringa. Nel secondo ad essere chiamata é una funzione statica, nel terzo una normale funzione membro, che ha bisogno di un "this" per operare, e che viene estratto dall'istanza di AClass che viene passata come _1.
Nel quarto esempio vediamo che é posibile passare esplicitamente a bind un'istanza della classe costruita al volo. In questo caso il primo segnaposto viene dedicato al parametro della funzione. Il quinto esempio permette di fare a meno dell'uso di ogni segnaposto, dato che bind prende in input il puntatore alla funzione, il puntatore a un oggetto della classe creato al volo e il parametro passato alla funzione.
Parte sull'uso di bind, nel capitolo nove, all'interno della terza parte, dedicata ai functor e alla programmazione di più alto ordine.
L'uso di boost::bind é estremamente flessibile. Nell'esempio che segue si vede come il confronto con std::mem_fun_ref la veda vincente.
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include "boost/bind.hpp"
using namespace std;
class status {
string name_;
bool ok_;
public:
status(const string& name) : name_(name), ok_(true) {}
void breakIt() {
ok_=false;
}
bool isBroken() const {
return ok_;
}
void report() const {
cout << name_ << " is " << (ok_ ? "working" : "broken") << endl;
}
};
void bind01() {
vector<status> statuses;
statuses.push_back(status("status 1"));
statuses.push_back(status("status 2"));
statuses.push_back(status("status 3"));
statuses.push_back(status("status 4"));
statuses[1].breakIt();
statuses[2].breakIt();
cout << "Using a classic for loop" << endl;
for (vector<status>::iterator it = statuses.begin(); it != statuses.end(); ++it) {
it->report();
}
cout << "Using for_each and mem_fun_ref" << endl;
for_each(statuses.begin(), statuses.end(), mem_fun_ref(&status::report));
cout << "Using for_each and boost::bind" << endl;
for_each(statuses.begin(), statuses.end(), boost::bind(&status::report, _1));
}
Il bello di boost::bind é che funziona quando il parametro passato, identificato con il segnaposto _1, può essere un valore, un puntatore, o anche uno smart pointer. Usando le funzionalità offerte dalla libreria standard (in attesa della riscrittura 0x) occorre usare l'adattatore mem_fun_ref, mem_fun, o disperarsi per l'assenza di disponibilità per smart pointer.
Nell'esempio seguente vediamo come boost::bind lavori indifferentemente con funzioni libere o membri di classi. Bisogna solo fare attenzione ai parametri passati via segnaposto. Nel caso di funzioni membro non statiche, che richiedano quindi un "this" su cui operare, il primo segnaposto deve essere dedicato a un oggetto della classe a cui fare riferimento.
#include <iostream>
#include <string>
#include "boost/bind.hpp"
using namespace std;
class AClass {
public:
void printString(const string& s) const {
cout << "Member function: " << s << endl;
}
static void staticPrintString(const string& s) {
cout << "Static member function: " << s << endl;
}
};
void printString(const string& s) {
cout << "Free function: " << s << endl;
}
void bind02() {
boost::bind(&printString, _1)("Hello!"); // 1
boost::bind(&AClass::staticPrintString, _1)("Hello!"); // 2
AClass c;
boost::bind(&AClass::printString, _1, _2)(c, "Hello!"); // 3
boost::bind(&AClass::printString, AClass(), _1)("Hello!"); // 4
boost::bind(&AClass::printString, AClass(), "Hello!")(); // 5
}
Nel primo esempio boost::bind invoca una funzione libera, passandole via il primo segnaposto, _1, una stringa. Nel secondo ad essere chiamata é una funzione statica, nel terzo una normale funzione membro, che ha bisogno di un "this" per operare, e che viene estratto dall'istanza di AClass che viene passata come _1.
Nel quarto esempio vediamo che é posibile passare esplicitamente a bind un'istanza della classe costruita al volo. In questo caso il primo segnaposto viene dedicato al parametro della funzione. Il quinto esempio permette di fare a meno dell'uso di ogni segnaposto, dato che bind prende in input il puntatore alla funzione, il puntatore a un oggetto della classe creato al volo e il parametro passato alla funzione.
Iscriviti a:
Post (Atom)