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();
}
Nessun commento:
Posta un commento