Programmazione orientata agli oggetti in ANSI-C. Un esempio. Tecnica

Ogni oggetto viene manipolato tramite void *. Se da un lato ciò semplifica la scrittura del codice, non evita disastri: manipolare un non oggetto o un oggetto sbagliato in un metodo o peggio, selezionare un metodo da una descrizione di classe che non lo contiene, causerà un sacco di problemi. Prendiamo ad esempio una chiamata ad un metodo linkato dinamicamente. New() produce un cerchio e il selettore draw() viene applicato al cerchio stesso:

void * p = new(Circle, 1, 2, 3);
draw(p);

Il selettore crede e dereferenzia il risultato di classOf():
void draw (const void * _self) {
const struct PointClass * class = classOf(_self);
assert(class —> draw);
class —> draw(_self);
}

Il metodo selezionato crede e dereferenzia _self che è il valore del puntatore originale prodotto da new():

static void Circle_draw (const void * _self) {
const struct Circle * self = _self;
printf("circle at %d,%d rad %d\n",
x(self), y(self), self —> rad);
}

classOf() dereferenzia un puntatore. Come piccola consolazione, però verifica che il risultato sia un puntatore non nulla.

const void * classOf (const void * _self) {
const struct Object * self = _self;
assert(self);
assert(self —> class);
return self —> class;
}

In generale, ogni assegnamento di un generico valore void * ad un puntatore a qualche struttura è un'operazione delicata e ne dovrebbe essere caldamente verificata la fattibilità. Abbiamo progettato i nostri metodi per essere polimorfici, ossia il compilatore ANSI C non si preoccupa di garantirne la polimorficità. Dobbiamo inventare il controllo dinamico del tipo in modo da limitare i danni che possono essere causati dall'uso improprio di oggetti o strutture.

Fortunatamente, i nostri valori void * sanno a che cosa stanno puntando: essi puntano ad oggetti che ereditano tutti da Object e, di conseguenza, contengono il componente .class che punta alla loro descrizione di classe. Ogni descrizione di classe è unica, di conseguenza il valore del puntatore in class può essere usato per determinare se un oggetto appartiene ad una particolare classe.
int isA (const _self, const Class @ class);
int isOf (const _self, const Class @ class);

Questi sono due nuovi metodi staticamente linkati per Object e quindi per ogni altro oggetto: isA() è vero se un oggetto appartiene direttamente ad una classe specifica; isOf() è vero se un oggetto è derivato da una classe specifica. Si possono introdurre i seguenti assiomi:

isA(0, anyClass) always false
isOf(0, anyClass) always false
isOf(x, Object) true for all objects

Ne consegue che un altro metodo staticamente linkato diventa molto utile per Object

if isOf(_self,class) è vero, cast() restituisce il suo argomento _self, altrimenti cast() termina il processo delle chiamate.
void * cast (const Class @ class, const _self);
Cast() è ora pronto per sostituire assert() nella maggior parte dei casi in cui sia utile effettuare controlli per evitare danni o usi impropri del codice. Ogni qualvolta quindi che non siamo sicuri dei parametri, effettuiamo un'operazione di cast() per evitare la generazione di valori inaspettati.

La funzione viene anche usata per dereferenziare i puntatori nell'ambito dell'importazione di un metodo o selettore.

cast(someClass, someObject);

Da notare che i parametri di cast() hanno l'ordine naturale tipico di un'operazione di casting: la classe è scritta alla sinistra dell'oggetto di cui effettuare il casting. IsOf(), tuttativa, prende gli stessi parametri nell'ordine opposto perchè in uno statement if ci chiediamo se un oggetto “è di” una classe particolare.
struct Circle * self = cast(Circle, _self);
Sebbene cast() accetti _self con un qualificatore const, restituisce il valore senza const per evitare appunto messaggi di errore nell'assegnamento. Il comportamento è analogo in C ANSI standard, ad esempio bsearch() fornisce un risultato void * per una tabella passata come const void *.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend