Notice: this article is currently available in italian only.
I will translate it soon. You may contact me via comments if you want me to “prioritize” this article first.
template<typename T>
class Fill {
private:
T *array;
long _size;
T _value;
public:
Fill(T *a) : array(a) {}
Fill &size(long s) { _size = s; return *this; }
Fill &with(T value) { _value = value; return *this; }
~Fill() {
for(long i=0; i<_size; i++) array[i] = _value;
}
};
Utilizzo snippet:
int array[10];
Fill<int>(array).size(10).with(1);
Ecco un po’ di teoria di cosa succede.
RAII è una tecnica che permette di sfruttare una caratteristica del c++ che lo differenzia dai linguaggi con garbage collector (Java, ad esempio): la certezza di quando il distruttore della classe verrà chiamato.
L’idea è di sfruttare entrata ed uscita dallo scope di una variabile per effettuare acquisizione e deallocazione delle risorse. O per dirla in altri termini, per eseguire istruzioni all’ingresso e all’uscita di uno scope.
In questo caso stiamo creando una istanza anonima della classe Filler.
Alla sua inizializzazione passiamo al costruttore
Fill<int>(T *a) : array(a) {}
un array, che vogliamo riempire. Il costruttore lo memorizzerà nel suo field “array”.
Con il metodo “size” diciamo quanti elementi dell’array vogliamo riempire, mentre col metodo “with” impostiamo il valore con cui riempire l’array.
Entrambi questi metodi tornano un riferimento a “this”, ossia all’istanza corrente, in modo da “tenerla viva” nello scope, e permettendo di effettuare method chaining.
Infine, quando la variabile scompare dallo scope (ossia subito, visto che è anonima), viene chiamato il distruttore, che contiene il ciclo for che riempie l’array col valore che abbiamo impostato.
E’ interessante notare come, non essendoci nessun metodo che esplicitamente riempie l’array, l’ordine delle chiamate è perfettamente invertibile: avrei infatti potuto ugualmente scrivere
Fill<int>(array).with(1).size(10);
E funzionerebbe nello stesso identico modo, dato che il riempimento vero e proprio verrà comunque effettuato nel distruttore.
Si tratta ovviamente di un esempio relativamente banale, ma che fa intuire la potenza della tecnica.
Basti pensare ad altre applicazioni, come l’apertura di un file con chiusura automatica quando la variabile RAII esce dallo scope, o una transazione che inizia nel costruttore, e viene automaticamente committata nel distruttore, o addirittura, nel c++11, l’esecuzione di una lambda quando la variabile RAII esce dallo scope.
class Scope {
public:
Scope(std::function<void()> onExit) : _onExit(onExit) {}
~Scope() { _onExit(); }
private:
std::function<void()> _onExit;
};
RAII viene molto usato sopratutto per gestire al meglio le eccezioni: non è infatti necessario un blocco finally come in Java, dato che sia in caso di eccezione che nel flusso normale la variabile viene comunque deallocata, e il distruttore invocato.