Programmazione orientata agli oggetti in ANSI-C. Implementazione della sottoclasse – Circle

Siamo così pronti a completare l'implementazione del cerchio, e possiamo quindi scegliere una qualsiasi delle tecniche presentate nella sezione precedente, come meglio ci aggrada.

Operare con la programmazione ad oggetti richiede però che vi sia un costruttore, possibilmente un distruttore, il metodo Circle_draw() e la descrizione di tipo Circle a fungere da legante. Per impratichirci con la programmazione ad oggetti, includeremo Circle.h e aggiungeremo le seguenti linee al programma di test. case ’c’:

p = new(Circle, 1, 2, 3);
break;

Ora possiamo osservare il seguente comportamento (in output) del programma

$ circles p c
"." at 1,2
"." at 11,22
circle at 1,2 rad 3
circle at 11,22 rad 3

Il costruttore del cerchio riceve 3 argomenti: prima le coordinate del punto centrale del cerchio e quindi il raggio. L'inizializzazione della parte relativa al punto è compito del costruttore del punto che "consumerà" parte della lista degli argomenti di new(). Al costruttore del cerchio viene demandato di inizializzare i rimanenti argomenti dalla linea degli argomenti, e quindi andrà ad inizializzare il cerchio.

Un costruttore di una sottoclasse dovrebbe fare in modo che sia il costruttore della superclasse ad effettuare le opportune inizializzazioni proprio per la parte che compete alla superclasse. Una volta che il costruttore della superclasse ha terminato l'inizializzazione che gli compete, il costruttore della sottoclasse completa l'inizializzazione e trasforma l'oggetto della superclasse in un oggetto della sottoclasse.

Per i cerchi significa che dovremmo chiamare Point_ctor(). Come tutti i metodi linkati dinamicamente, questa funzione viene dichiarata static e quindi nascosta dentro Point.c. Comunque sia, possiamo ottenere la funzione per mezzo del descrittore di tipo Point che è disponibile in Circle.c

static void * Circle_ctor (void * _self, va_list * app)
{ struct Circle * self =
((const struct Class *) Point) —> ctor(_self, app);
self —> rad = va_arg(* app, int);
return self;
}

Dovrebbe essere ora chiaro perchè passiamo l'indirizzo app ad ogni costruttore e non la lista va_list stessa: new() chiama il costruttore della sottoclasse, che chiama il costruttore della sua superclasse e così via. Il costruttore più esterno della gerarchia è il primo ad effettuare materialmente qualcosa e consuma gli argomenti della lista passata a new() che gli competono. Gli argomenti rimanenti sono disponibili alla successiva sottoclasse e così fino all'ultimo, il quale è consumato dalla sottoclasse ultima a sua volta, ossia viene consumato dal costruttore direttamente chiamato da new().

La distruzione dell'oggetto avviene invece nell'ordine inverso: delete() chiama il distruttore della sottoclasse, il quale dovrebbe distruggere le sue proprie risorse e quindi chiamare il distruttore della superclasse diretta che provvederà a sua volta alla distruzione delle risorse che gli competono e così via.

La costruzione di un oggetto avviene a partire dalla superclasse prima che dalla sottoclasse, la distruzione avviene invece in senso opposto, dalla sottoclasse prima che dalla superclasse, in altre parole prima la parte del cerchio e poi quella del punto. Qui, in questo caso, non c'è niente che va fatto.

Abbiamo lavorato precedentemente su Circle_draw(). Usiamo i componenti visibili e codifichiamo il file di rappresentazione Point.h come segue:

struct Point {
const void * class;
int x, y; /* coordinates */
};
#define x(p) (((const struct Point *)(p)) —> x)
#define y(p) (((const struct Point *)(p)) —> y)

ora possiamo usare le macro di accesso per Circle_draw();

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);
}

move() è linkata staticamente ed è ereditata dall'implementazione del punto. Concludiamo l'implementazione del cerchio definendo la descrizione di tipo che è la sola parte visibile globalmente di Circle. c. Mentre sembra che abbiamo una strategia sostenibile per distribuire il codice del programma fra interfacce, rappresentazioni e file di implementazione, l'esempio di punti e cerchi non ha evidenziato particolari problematiche: se un metodo dinamicamente linkato come Point_draw() non viene sovrascritto nella sottoclasse, il descrittore di tipo della sottoclasse deve puntare alla funzione implementata nella superclasse. Il nome della funzione è però definito statico, così il selettore non può essere modificato.

Vedremo una soluzione elegante a questo problema nel capitolo 6. Per ovviare in questo caso al problema non faremo uso di static in questo caso, e dichiareremo l'header della funzione solo nella file di implementazione della sottoclasse e useremo il nome della funzione per inizializzare il descrittore di tipo per la sottoclasse.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend