Nontipi come parametri di template

Continuo la lettura di C++ Templates: The Complete Guide, affrontando il terzo capitolo, che tratta i parametri di template che non sono tipi.

Abbiamo visto nei capitoli precedenti come specificare i tipi che parametrizzano il nostro template, sia esso per funzioni o per classi. Qui vediamo come parametrizzare la nostra template usando non un tipo ma un valore.

Curiosamente, per mostrare l'utilizzo di questa feature relativamente avanzata, si fa ricorso ad un esempio che non coinvolge l'uso di un container STL, cosa che é stata fatta per la prima definizione di una classe template.

In pratica il nostro esempio é l'implementazione di uno stack che si appoggia su un array C-style, dove però la dimensione dell'array viene parametrizzata con un parametro, un intero, deciso dall'utente.

Ecco qui a seguire la definizione della classe:

#include <stdexcept>
#include <iostream>

using namespace std;

template <typename T, int MAXSIZE>
class Stack {
private:
T elements[MAXSIZE];
int numElems; // current number of elements
public:
Stack() : numElems(0) {}
void push(const T&);
void pop();
T top() const;
bool empty() const {
return numElems == 0;
}
bool full() const {
return numElems == MAXSIZE;
}
};

template <typename T, int MAXSIZE>
void Stack<T,MAXSIZE>::push (T const& elem)
{
if (numElems == MAXSIZE) {
throw out_of_range("Stack<>::push(): stack is full");
}
elements[numElems++] = elem;
}

template<typename T, int MAXSIZE>
void Stack<T,MAXSIZE>::pop ()
{
if (numElems <= 0) {
throw out_of_range("Stack<>::pop(): empty stack");
}
--numElems; // decrement number of elements
}

template <typename T, int MAXSIZE>
T Stack<T,MAXSIZE>::top () const
{
if (numElems <= 0) {
throw out_of_range("Stack<>::top(): empty stack");
}
return elements[numElems-1]; // return last element
}

Notiamo che il template ora dipende dal tipo T e dall'intero MAXSIZE, questo viene usato per determinare la dimensione dell'array sottostante e per effettuare controlli sull'accesso ai dati dello stack.

Il codice per verificare il funzionamento della classe é il seguente:

#include <iostream>
using namespace std;
//...

int main() {
try {
Stack<int,20> int20Stack; // stack of up to 20 ints
int20Stack.push(7);
cout << int20Stack.top() << endl;
int20Stack.pop();

Stack<string,40> stringStack; // stack of up to 40 strings
stringStack.push("hello");
cout << stringStack.top() << endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() << endl;
return EXIT_FAILURE; // exit program with ERROR status
}
}

Notiamo come nell'instanziazione della nostra template specifichiamo come secondo parametro un intero, che sarà la dimensione massima del nostro stack.

Il resto del capitolo é dedicato all'approfondimento del tema, con dettagli sull'uso di valori di default e restrizioni sul tipo di valori che si possono passare.

Quanti giorni tra due date: difftime

Vogliamo calcolare la differenza in giorni tra due dati istanti.

Per istante intendiamo il numero di secondi passati dalla mezzanotte del primo gennaio 1970 UTC (l'inizio del tempo UNIX), un valore integrale che viene gestito da variabili di tipo time_t.

Come spesso accade, risolviamo il nostro problema riducendolo a uno più semplice, per poi adattare il risultato ai nostri bisogni.

Calcoliamo quindi i secondi di differenza tra due tempi usando la funzione difftime, definita in ctime con questo prototipo:

double difftime(time_t t2, time_t t1);

dove t2 é il tempo più lontano dall'inizio del tempo. Se si mette in t2 il primo tra i due tempi si otterrà un risultato negativo.

Dividiamo poi il risultato per il numero di secondi in un giorno, 86400, ottenendo il risultato atteso:

double days = difftime(end, start) / 86400;

Notiamo che il numero di giorni é espesso come numero decimale, dato che si fa una differenza al secondo, quindi si ottiene una risposta che potrebbe essere del tipo "circa otto giorni e mezzo".

Per passare da una data in formato leggibile a un umano a una nel formato accettato da difftime usiamo la funzione mktime(), anch'essa definita in ctime:

time_t mktime(tm* pTime);

che non fa altro che convertire il contenuto di un oggetto di tipo tm nel numero di secondi passati dall'inizio del tempo UNIX. In caso di errore la funzione ritorna -1.

La creazione di un oggetto di tipo tm, una struttura definita in ctime, sarebbe banale, se non ci fosse da tener presente alcune sottigliezze.

Bisogna infatti specificare l'attimo definendo tutte le sue componenti in un modo che non é sempre naturale.
Secondi, minuti e ore non danno problemi - anche se vanno indicati come li ho scritti, non partendo dalle ore, come sembrerebbe spontaneo.
Segue poi il giorno del mese, nessun problema.
Poi il mese, partendo da Gennaio che vale 0 fino a dicembre che é 11.
E infine l'anno a partire dal 1900 (ovvero, per il 2000 occorre mettere 100).

Dunque, per creare un oggetto tm corrispondente alla mezzanotte del 2 ottobre 2000 dobbiamo scrivere:

tm tmStart = {0, 0, 0, 2, 9, 100};

Per testare la funzionalità scriviamo questa funzioncina:

#include
#include

using namespace std;
// ...
void testDiff() {
tm tmStart = {0, 0, 0, 2, 9, 100};
time_t start = mktime(&tmStart);
if(start == -1) {
cout << "Bad start date." << endl;
return;
}

tm tmEnd = {0, 0, 0, 3, 7, 109};
time_t end = mktime(&tmEnd);
if(end == -1) {
cout << "Bad end date." << endl;
return;
}

double days = difftime(end, start) / 86400;
cout << "difference = " << days << " days" << endl;
return;
}

Template per classi

Nel capitolo tre di C++ Templates: The Complete Guide si affrontano i template per classi. L'esempio che si fa é, anche qui, un classico, uno stack generico.

Un po' discutibile la scelta di usare come struttura dati sottostante un vector STL, dato che in questo modo si finisce per usare una classe template per spiegare come usare i template per classi. Insomma, si cade nella ricorsione.

Il vantaggio é che si semplifica molto il codice, rendendo forse più facile la comprensione al principiante - a patto che si fidi e non si faccia troppe domande sulla classe vector.

Anche questo capitolo mi sembra ben fatto, dando una buona introduzione alle funzionalità di base e a quelle meno comuni ma che é buona cosa sapere che ci sono e come usarle, come la specializzazione del template per uno specifico tipo e l'uso di argomenti di default nella definizione di un template.

Qui riporto solo il codice per un esempio di base, con la definizione della classe generica Stack:

#include <vector>
#include <stdexcept>
#include <iostream>

using namespace std;

template<class T>
class Stack {
private:
vector<T> elements;

public:
void push(T const& element) {
// append copy of passed element
elements.push_back(element);
}
void pop();
T top() const;
bool empty() const {
return elements.empty();
}
};

template<class T>
void Stack<T>::pop()
{
if (elements.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
elements.pop_back();
}

template<class T>
T Stack<T>::top() const
{
if (elements.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
return elements.back();
}

Notiamo come pop() e top() possano tirare eccezioni nel caso si cerchi di chiamarle su uno stack vuoto. Il codice che usa questa classe, perciò, dovrà tenere conto di questa possibilità:

try {
Stack<string> stringStack;

stringStack.push("hello");
cout << stringStack.top() << endl;
stringStack.pop();
stringStack.pop();
}
catch (const exception& ex) {
cerr << "Exception: " << ex.what() << endl;
}

Template per funzioni

Cominciata la lettura di C++ Templates: The Complete Guide, di cui si parlava nel post precedente.

Letto il capitolo introduttivo, in cui gli autori giustificano le loro convenzioni di scrittura del codice che dovremmo accettare in tutto il libro (e che a me sembrano un poco innaturali), e il capitolo due, che tratta i template per le funzioni.

Non male, il capitolo, spiegano le cose piuttosto bene, e scendendo in dettagli che in genere sono tralasciabili, ma comunque é bello sapere dove si può andare a vedere quando venga la necessità di rivederseli.

Qui riporto un piccolo esempio di base per una funzione templatizzata, la solita max(), giusto come riferimento.

Tipicamente definiamo un template per una funzione in questo modo:

template <typename T>
inline const T& myMax (const T& a, const T& b)
{
return a < b ? b : a;
}

In questo caso la funzione é inline, data la sua semplicità, e per mantenere le prestazioni di quella che é la controparte C, che é implementata come macro.

Il vantaggio del template C++ é che é type-safe e a prova di conflitti con le precedenze degli operatori (rimando al libro che sto leggendo per i dettagli).

Per vedere testare il codice ho scritto queste righe di codice:

int three = 3;
cout << "myMax<int> is " << myMax(three, 8) << endl;

string one("one");
string two("two");
cout << "myMax<string> is " << myMax(one, two) << endl;

In cui la mia funzione parametrizzata viene testata per interi e stringhe.

Che dire. Funziona.

C++ Templates: The Complete Guide

Sento il bisogno di rinfrescare le mie conoscenze di base sulle template, mi sono così preso questo libro che mi pare prometta bene.

Trattasi di:
C++ Templates: The Complete Guide
di David Vandevoorde, Nicolai M. Josuttis
Addison-Wesley Professional 2003

Un utile link per chi fosse interessato al libro é questo. Si tratta di alcune pagine web che lo Josuttis gli ha dedicato, includendo anche riferimenti al codice sorgente per gli esempi affrontati nel libro.

Un altro link da considerare é quest'altro, che punta alla home del libro nel sito dell'altro autore, Vandevoorde

A quanto ho visto da una rapida scorsa, non si tratta di un manuale sulla Standard Template Library ma piuttosto una guida all'uso del concetto di template in C++.

Dato che é proprio questo che mi interessa al momento, direi che questo tomo é quello che fa per me. Almeno credo.

Seguiranno dettagli nei prossimi post.

POSIX Mutex

Nel post precedente abbiamo visto come gestire diversi thread in una applicazione scritta in C++. Ora vediamo come gestire i problemi di concorrenza, sempre nell'ambito delle specifiche POSIX.

Il concetto di semaforo viene implementato con il Mutex (che sta per mutual exclusion, mutua esclusione).

Dovrebbe servire a spiegare il concetto questo esempio.

Scriviamo una piccola applicazione in cui una classe somma dei dati contenuti al suo interno dividendo il lavoro su quattro thread. Il punto da tenere sotto controllo é dove viene letto e incrementato il totale corrente, che risulta essere la risorsa condivisa tra i diversi thread.

Infatti si regolamentasse il suo accesso con un mutex, potrebbe succedere che un thread legga il suo valore dopo di un altro ma scriva il suo risultato aggiornato prima. Di conseguenza il suo lavoro andrebbe perso.

Vediamo il codice:
#include <errno.h>
#include <iostream>
#include <sstream>

using namespace std;

class HugeWork {
public:
HugeWork(int res, int a, int b, int c, int d) : m_result(res) {
m_values[0] = a;
m_values[1] = b;
m_values[2] = c;
m_values[3] = d;

pthread_mutex_init(&m_mutex, 0);
}

~HugeWork() {
pthread_mutex_destroy(&m_mutex);
}

double getResult();
private:
static void* increase(void*);

int m_result;
int m_values[4];

pthread_t m_thread_id[4];
pthread_mutex_t m_mutex;
};

double HugeWork::getResult() {
ostringstream os;
for (int i = 0; i < 4; ++i) {
if (pthread_create(&m_thread_id[i], NULL, &increase, this) < 0) {
cout << "Error creating thread " << i << ": " << errno << endl;
return 0;
}

os << "Thread " << m_thread_id[i] << " created (" << i << ")" << endl;
cout << os.str();
os.str("");
}

// wait for all the threads to complete
for(int i = 0; i < 4; ++i) {
os << "Joining thread " << m_thread_id[i] << endl;
cout << os.str();
os.str("");

pthread_join(m_thread_id[i], 0);
}

return m_result;
};

// notice: should be static for pthread requirements
void* HugeWork::increase(void* ptr) {
HugeWork* hw = static_cast<HugeWork*> (ptr);


sleep(1);

int result = 0;
for (int i = 0; i < 4; ++i) {
result += hw->m_values[i];
}

pthread_mutex_lock(&hw->m_mutex);
cout << "increasing " << result << endl;
hw->m_result += result;
pthread_mutex_unlock(&hw->m_mutex);

return 0;
}

int main(int argc, char** argv) {
HugeWork hw(1, 1, 2, 3, 4);

cout << "Final result: " << hw.getResult() << endl;
}

POSIX Threads

Una semplice applicazione C++ per illustrare le basi della programmazione multithreading secondo le specifiche POSIX.

Molti dettagli sono dipendenti dall'implementazione, in questo caso lo sviluppo é stato fatto su Cygwin.

Disegnamo una classe, CountDown, che permette di fare il conto alla rovescia.

Dal main vediamo come viene usata, si istanzia un oggetto della classe e si chiama su di esso il metodo run():

CountDown cd1("two x two", 2, 2);
// ...
cd1.run();

Notiamo che tra i membri della classe abbiamo messo un oggetto di tipo pthread_t che rappresenta l'identificatore del thread in cui viene eseguita:

pthread_t m_thread_id;

Il metodo run() crea un nuovo thread salvando nella variabile membro m_thread_id l'identificatore del thread creato e passando l'indirizzo della metodo privato go() che cura i dettagli implementativi della classe.

pthread_create(&m_thread_id, NULL, &go, this)

Il metodo go() é dichiarato static per ragioni di compatibilità con pthread, che é una libreria C e quindi nulla sa di classi e oggetti.

La prima cosa che facciamo in go() é rendere disponibile l'accesso alla classe su cui stiamo lavorando. Il "trucco" a cui abbiamo fatto ricorso é quello di passare this dalla run() via pthread_create come puntatore a void, e qui farlo tornare un puntatore a CountDown con un (piuttosto brutale) static_cast.

CountDown* cd = static_cast(ptr);

E quindi viene eseguito in count down usando i settaggi per l'oggetto corrente. Notiamo l'uso della funzione sleep() per scandire il tempo:

sleep(cd->m_sleep);

Per evitare che un oggetto venga distrutto quando il suo thread é ancora in corso di esecuzione, facciamo una join (ovvero, attendiamo la terminazione del thread) nel distruttore:

pthread_join(m_thread_id, 0);

E questo é, più o meno, tutto. A seguire il codice completo:

#include <string>
#include <iostream>
#include <unistd.h>
#include <errno.h>
#include <sstream>

using namespace std;

class CountDown {
public:
CountDown(const char* id, int start, int sleep =1) :
m_id(id), m_start(start), m_sleep(sleep), m_thread_id(0) {}

~CountDown();

void run();

private:
static void* go(void*);

string m_id;
int m_start;
int m_sleep;
pthread_t m_thread_id;
};

// ensure the object is destroyed only when the thread is terminated
CountDown::~CountDown() {
pthread_join(m_thread_id, 0);
cout << "Dtor completed on " << m_id << endl;
}

// notice: should be static for pthread requirements
void* CountDown::go(void* ptr) {
CountDown* cd = static_cast<CountDown*>(ptr);

ostringstream os;
os << "running " << cd->m_id << endl;
cout << os.str();

for(int i = cd->m_start; i > 0; --i) {
os.str("");
os << cd->m_id << " " << i << endl;
cout << os.str();
sleep(cd->m_sleep);
}

cout << "count down done for " << cd->m_id << endl;
return 0;
}

void CountDown::run() {
if(pthread_create(&m_thread_id, NULL, &go, this) < 0) {
cout << "Error creating thread " << errno << endl;
return;
}

ostringstream os;
os << "Thread id " << m_thread_id << " created (" << m_id << ")" << endl;
cout << os.str();
}

/*
*
*/
int main(int argc, char** argv) {
CountDown cd1("two x two", 2, 2);
CountDown cd2("three", 3);
CountDown cd3("four", 4);

cd1.run();
cd2.run();
cd3.run();

ostringstream os;
os << "All threads are running" << endl;
cout << os.str();
}

Anatre in formazione

Completiamo il discorso sul pattern Strategy secondo quanto si trova nel primo capitolo di Head First - Design Patterns, descrivendo la gerarchia di anatre e poi le gerarchie di comportamenti associati.

La classe base delle anatre é Duck, una classe astratta che ha due dati membro che sono poi le due strategie dell'anatra: il modo di volare e il modo di emettere un verso.

Notiamo che, per essere utilizzabile, un anatra reale dovrà definire le due strategie, in modo che possano essere coerentemente invocati i metodi fly() e quack() su di essa. Il metodo swim é comune a tutte le implementazioni, e quindi viene definito in Duck.

package hf.strategy;

public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;

public Duck() {
}

public void setFlyBehavior (FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}

public void fly() {
flyBehavior.fly();
}

public void quack() {
quackBehavior.quack();
}

public void swim() {
System.out.println("All ducks float, even decoys!");
}
}


La definizione di un'anatra reale richiederà la specificazione delle strategie adottate. Vediamo ad esempio la classe MallardDuck:


package hf.strategy;

public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}

Strategie

Restano ora da definire le strategie. Al top della gerarchia di una strategia ci sarà un'interfaccia, che dichiara il metodo che viene richiesto. Qui vediamo la strategia di volo:

package hf.strategy;

public interface FlyBehavior {
public void fly();
}

E qui la strategia di starnazzamento:

package hf.strategy;

public interface QuackBehavior {
public void quack();
}


Una classe reale utilizzerà una implementazione coerente con i suoi scopi delle strategie richieste. MallardDuck richiedeva queste:

package hf.strategy;

public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}

// ---
package hf.strategy;

public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}

La strategia dell'anatra

Nel primo capitolo di Head First - Design Patterns si tratta il pattern Strategy a partire dalla progettazione di una gerarchia di classi che descrivono il comportamento di diversi tipi di anatre.

Il problema é quello di dare alla gerarchia una robustezza tale da permetterle di reggere agevolemente a cambiamenti futuri delle specifiche di progetto.

Il pattern Strategy definisce una famiglia di algoritmi, li incapsula, e li rende intercambiabili. Strategy fa un modo che l'algoritmo cambi indipendentemente dal client che lo usa.

Nel corso del capitolo vengono citati tre importanti principi, che riporto qui a seguire.

Il fatto che di costante nello sviluppo software sia solo il cambiamento conduce al seguente principio di progettazione: identifica gli aspetti dell'applicazione che cambiano e separali da quelli che non mutano.

Un altro importante principio: programma verso un interfaccia e non verso una implementazione. Questo permette una maggior astrazione del codice e quindi un miglior adattamento del codice al cambiamento.

Un terzo principio: preferisci la composizione all'ereditarietà. Permette molta più flessibilità, dato che permette di cambiare il comportamento di una classe a runtime.

Detto questo, vediamo le specifiche del nostro progetto.

Vogliamo gestire una gerarchia di anatre, in modo da poter gestire classi che rappresentano sia animali veri e propri che oggetti come un anatra di gomma o un anatra da richiamo.

L'idea é che alcuni algoritmi che potrebbero sembrare scontati nella nostra gerarchia (il volare dell'anatra, nel nostro esempio) in realtà non lo sono.

Nell'implementazione originale, ogni nostra anatra poteva volare, emettere un verso (quack!) e nuotare. Creando nuove classi derivate, abbiamo cozzato contro una variazione della progettazione originale: esistono anatre che non emettono versi e anatre che non volano.

Abbiamo quindi rifattorizzato la classe di base per riflettere questo cambiamento: l'anatra generica si appoggia su classi esterne che implementano la strategia della specifica anatra.

Avremo quindi una gerarchia di classi basate in Duck, la classe astratta da cui tutte le nostre anatre derivano, e una gerarchia per ogni strategia che Duck utilizza.

Nel nostro caso abbiamo una interfaccia FlyBehavior per la strategia di volo e QuackBehavior per la strategia di starnazzamento.

Nei post seguenti vedremo il codice per implementare questa soluzione.