Programmazione orientata agli oggetti in ANSI-C. Implementazione – Class

Class è una sottoclasse di Object, così possiamo semplicemente ereditare il metodo per il confronto e mostrare il risultato. Il distruttore ritorna un puntatore nullo per evitare che venga elimanto lo spazio occupato dalla descrizione di classe tramite l'uso di delete().

static void * Class_dtor (void * _self)
{ struct Class * self = _self;
fprintf(stderr, "%s: cannot destroy class\n", self—>name);
return 0;
}

Qui invece ecco una funzione di accesso per ottenere la superclasse a partire da una descrizione di classe.

const void * super (const void * _self)
{ const struct Class * self = _self;
assert(self && self —> super);
return self —> super;
}

La sola difficoltà è l'implementazione del costruttore di Class perchè è proprio grazie al costruttore che viene inizializzata una nuova descrizione di classe, ossia da dove l'ereditarietà parte e dove i nostri quattro metodi base possono essere sovrascritti. Facciamo riferimento alla sezione 6.4 su come una nuova descrizione di classe viene creata.

const void * Any =
new(Class, "Any", Object, sizeOf(o),
differ, Any_differ,
0);

Ciò significa che il nostro costruttore di Class riceve il nome, la superclasse, la dimensione degli oggetti per una nuova descrizione di classe. Iniziamo l'implementazione trasferendo queste informazioni dalla lista degli argomenti.

static void * Class_ctor (void * _self, va_list * app)
{ struct Class * self = _self;
self —> name = va_arg(* app, char *);
self —> super = va_arg(* app, struct Class *);
self —> size = va_arg(* app, size_t);
assert(self —> super);

Self non può essere un puntatore nullo perchè altrimenti non avremmo trovato questo metodo. Super, tuttavia, potrebbe essere zero ma sarebbe una pessima idea.

Il prossimo step consiste nella gestione dell'ereditarietà. Dobbiamo copiare il costruttore e tutti i metodi dalla descrizione della superclasse in super nella nuova descrizione di classe in self.

const size_t offset = offsetof(struct Class, ctor);
...
memcpy((char *) self + offset, (char *) self —> super
+ offset, sizeOf(self —> super) — offset);

Ipotizzando che il costruttore sia il primo metodo in struct Classe, useremo la macro offsetof() dello standard ANSI C per determinare dove la nostra copia deve partire. Fortunatamente, la descrizione di classe in super è derivata da Object e ha sizeOf() ereditata così possiamo calcolare esattamente quanti bytes copiare.

Sebbene questa soluzione sia un po' complessa da capire, sembra costituire sempre il miglior compromesso fra leggibilità ed usabilità. In alternativa, avremmo potuto copiare l'intera area in super e memorizzare il nuovo nome e così via.

L'ultima parte del costruttore di Class è responsabile di sovrascrivere tutti i metodi che sono stati specificati nella lista di argomenti passata a new(). Lo standard ANSI C non si permette di assegnare puntatori a funzioni da e per void*, così dobbiamo procedere ad effettuare opportuni casting:

{
typedef void (* voidf) (); /* generic function pointer */
voidf selector;
va_list ap = * app;
while ((selector = va_arg(ap, voidf)))
{ voidf method = va_arg(ap, voidf);
if (selector == (voidf) ctor)
* (voidf *) & self —> ctor = method;
else if (selector == (voidf) dtor)
* (voidf *) & self —> dtor = method;
else if (selector == (voidf) differ)
* (voidf *) & self —> differ = method;
else if (selector == (voidf) puto)
* (voidf *) & self —> puto = method;
}
return self;
}}

Come potremo vedere nella sezione 6.10 questa parte della lista degli argomenti è meglio condivisa fra tutti i costruttori di classe così la coppia selettore/metodo può essere specificata in qualsiasi ordine. Pertanto, non incrementeremo *app, ma passeremo una copia ap di questo valore ad va_arg().

Memorizzare i metodi in questo modo ha però alcune conseguenze: se nessun costruttore di classe viene coinvolto in un selettore, la coppia selettore/metodo viene banalmente ignorata, ma almeno non viene aggiunta alla descrizione di classe alla quale essa non appartiene. Se un metodo non ha il tipo appropriato, il compilatore ANSI C non rileverà l'errore perchè la lista variabile degli argomenti e il nostro casting impedisce il controllo dei tipi. Facciamo quindi affidamento sul programmatore che sarà in grado di effettuare il corretto match fra selettore e metodo.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend