Programmazione orientata agli oggetti in ANSI-C. Un’altra implementazione – Atom

Per illustrare cosa possiamo fare con il costruttore e l'interfaccia del distruttore implementeremo atomi. Un atomo è un unico oggetto striga. Se due atomi contengono le stesse stringhe sono identici. Gli atomi sono facili da confrontare: differ() è vera se i due puntatori argomento sono diversi.

Gli atomi sono però più difficili da costruire e da distruggere: dovremo mantenere una lista circolare di tutti gli atomi e conteremo il numero di volte che un atomo viene clonato:

struct String {
const void * class; /* must be first */
char * text;
struct String * next;
unsigned count;
};
static struct String * ring; /* of all strings */
static void * String_clone (const void * _self)
{ struct String * self = (void *) _self;
++ self —> count;
return self;
}

La nostra lista circolare di tutti gli atomi è marcata come ring (anello), estesa attraverso il componente .next e gestita dal costruttore e dal distruttore di stringa. Prima il costruttore salva un testo verificando se non sia già presente nella lista. Il codice seguente viene inserito all'inizio della funzione String_ctor():

if (ring)
{ struct String * p = ring;
do
if (strcmp(p —> text, text) == 0)
{ ++ p —> count;
free(self);
return p;
}
while ((p = p —> next) != ring);
}
else
ring = self;
self —> next = ring —> next, ring —> next = self;
self —> count = 1;

Qualora trovassimo un atomo adatto, incrementeremo il suo contatore di riferimento, libereremmo la nuova stringa oggetto self e restituiremmo l'atomo p. Altrimenti, inseriamo la nuova stringa oggetto nella lista circolare e imposteremo il suo contatore di riferimento a 1.

Il distruttore evita che un atomo venga cancellato qualora il suo contatore di riferimento non sia pari a 0. Il seguente codice deve essere inserito all'inizio di String_dtor():

if (—— self —> count > 0)
return 0;
assert(ring);
if (ring == self)
ring = self —> next;
if (ring == self)
ring = 0;
else
{ struct String * p = ring;
while (p —> next != self)
{ p = p —> next;
assert(p != ring);
}
p —> next = self —> next;
}

Se il contatore di riferimento appena decrementato è positivo, restituisce un puntatore nullo così che la funzione delete() non agisce sull'oggetto. Altrimenti, eliminiamo il marcatore della lista circolare se la nostra stringa è l'ultima in esso contenuta oppure se rimuoviamo la stringa dalla lista.

Con questa implementazione la nostra applicazione della sezione 2.4 verifica che una stringa clonata è identica all'originale e quindi stampa:

sizeOf(a) == 16
ok
clone?

Sommario
Dato un puntatore ad un oggetto, il dynamic linkage ci permette di trovare funzioni specifiche rispetto al tipo: ogni oggetto inizia con un descrittore che contiene i puntatori alle funzioni applicabili all'oggetto. In particolare, se un descrittore contiene un puntatore ad un costruttore che inizializza l'area di memoria allocata dall'oggetto, e il puntatore al distruttore che reclama risorse acquisite da un oggetto prima che questo fosse cancellato.

Chiameremo tutti gli oggetti che condividono lo stesso descrittore come class (classe). Un oggetto è un'istanza di una classe, funzioni specifiche per il tipo di un oggetto sono chiamate metodi e i messaggi non sono altro che chiamate a queste funzioni. Useremo le funzioni selettore per localizzare e chiamare i metodi per un oggetto.

Attraverso i selettori e il linkage dinamico, lo stesso nome di funzione può intraprendere azioni diverse su classi diverse. Una funzione del genere viene detta polimorfica.

Le funzioni polimorfiche sono decisamente utili perchè forniscono un livello di astrazione concettuale: differ() confronta due qualsiasi oggetti - non abbiamo bisogno di ricordare che una particolare differ() è applicabile in una situazione concreta. Un metodo molto semplice e poco costoso per disporre di un tool di debug è la funzione polimorfica store() che stampa un qualsiasi oggetto su di un file descriptor.

Esercizi

Per vedere le funzioni polimorfiche in azione dobbiamo implementare Object e Set con il dynamic linkage. E' abbastanza complesso per quanto riguarda i Set perchè non possiamo memorizzare nel set elementi che già appartengono ad un set.

Ci dovrebbero essere più metodi per le stringhe: dobbiamo sapere la lunghezza della stringa, vogliamo assegnare ad una stringa un nuovo testo, dovremmo essere in grado di stampare una stringa. Le cose si fanno interessanti quando ci troviamo di fronte alle sottostringhe (substring).

Gli atomi sono molto più efficienti, se facciamo in modo di gestirli con una tabella hash. Può il valore di un atomo essere modificato?

String_clone() pone una sottile interrogazione: in questa funzione String dovrebbe essere lo stesso valore di self->class. Fa qualche differenza ciò che passiamo a new()?

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend