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:

#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