-- Introduzione
-- Obiettivi
Implementazione pratica
-- Dichiarazione di classe
Ereditarietà

 

Progetto Yed
Tecniche di programmazione ad oggetti con il linguaggio C.

Costruttore e distruttore

Una volta che l'implementazione dei metodi è stata realizzata, è giunto il momento ( tanto atteso ) di creare un'istanza della nostra classe simulata. Quindi, è giunto il momento di scrivere il costruttore e il distruttore di tale classe.
Grazie al pronto intervento del compilatore, una istanza di una classe C++ può essere dichiarata sia statica che dinamica, perchè nel caso di istanza statica il costruttore della classe viene invocato automaticamente con i parametri impostati. Nel caso di istanza dinamica, è l'utente il responsabile dell'inizializzazione dell'oggetto tramite l'utilizzo delle keywords
new e delete . Nel mondo C, l'inizializzazione statica ci è ovviamente ( per ora... ) impossibile, dunque istanzieremo le nostre classi sempre dinamicamente. E per questo, sono state create delle macro ad hoc, ed una tecnica di inizializzazione e distruzione molto simile a quella C++.
Partiamo dalle macro; riprendiamo il nostro header
yedstd.h , e precisamente le macro

#define New(x) (x *)_##x()
#define Delete(x,a) _D##x(a)

Come abbiamo già visto, il preprocessore C mette a disposizione un token importantissimo: il token '##'. Il suo significato è concatenazione di simboli. Nel nostro caso, la macro New(x) viene sostituita, nel codice in cui viene incontrata, con la sequenza (x *)_x() , vale a dire con l'invocazione della routine _x, la quale ritorna un puntatore ad un'area di memoria, trasformato tramite il cast a dimensione 'x' bytes. Ecco dunque creato il nostro costruttore dinamico. Basterà creare una funzione nel nostro file yedprova.c che abbia questa forma:

#include "yedstd.h"
#include "yedprova.h"

// COSTRUTTORE DINAMICO OGGETTO DI TIPO Cprova
Cprova *_Cprova(void)
   {
   ...
   }


int metodo(void *pVWork)
  {
  Cprova *this=pvWork;
  printf("Invocazione metodo: elemento interno [%d]\n",
         this->PRIVATE(foo_priv));
  return 0;
  }

Perchè utilizzare il carattere underscore ( '_' ) prima della stringa 'Cprova' ? Perchè il compilatore C si offenderebbe, se tentassimo di utilizzare come simbolo che identifica una funzione un simbolo che già identifica una struttura, cioè la nostra classe simulata. Dunque, lo concateniamo al carattere che storicamente alcuni linker utilizzano per la loro manipolazione di simboli, ma ovviamente la scelta del carattere, o della sequenza di caratteri, è del tutto libera.
Scrivere dunque nel nostro codice

#include "yedstd.h"
#include "yedprova.h"

....
Cprova *cWrk;
cWrk=New(Cprova);
....

equivale ad aver scritto

#include "yedstd.h"
#include "yedprova.h"

....
Cprova *cWrk;
cWrk=(Cprova *) _Cprova();
....

cioè equivale ad aver invocato il nostro costruttore della classe simulata. Se uno sviluppatore di software volesse scrivere un'altra classe, sarebbe sufficiente creare una funzione che abbia il nome assegnato alla classe preceduto dal carattere underscore ( '_' ) per creare il costruttore di questa classe invocabile sempre tramite l'esplosione della macro New .
Il distruttore della classe utilizza l'identico meccanismo di concatenazione. Questa volta, però, il simbolo della classe deve essere preceduto dalla sequenza _D , sequenza ovviamente modificabile a piacere.
Il codice da implementare della nostra classe Cprova assume quindi la seguente forma:

#include "yedstd.h"
#include "yedprova.h"

// COSTRUTTORE DINAMICO OGGETTO DI TIPO Cprova
Cprova *_Cprova(void)
  {
  ...
  }


// DISTRUTTORE DINAMICO OGGETTO DI TIPO Cprova Cprova
*_DCprova(void *this)
  {
  ...
  }


int metodo(void *pVWork)
  {
  Cprova *this=pvWork;
  printf("Invocazione metodo: elemento interno [%d]\n",
         this->PRIVATE(foo_priv));
  return 0;
  }

Cosa inserire in questi costruttori e distruttori? Le operazioni di inizializzazione e distruzione della classe simulata, ovviamente, fra cui alcune assolutamente vitali per poter utilizzare correttamente gli oggetti del tipo della classe simulata, operazioni concettualmente uguali per tutte le classi simulate create.
Innanzitutto, l'oggetto è dinamico, quindi occorre prima di tutto allocarlo in memoria. La nostra vecchia amica malloc ci viene subito in soccorso:

#include <stdio.h>
#include "yedstd.h"
#include "yedprova.h"

// COSTRUTTORE DINAMICO OGGETTO DI TIPO Cprova
Cprova *_Cprova(void)
  {
  Cprova *this;

  if((this=(Cprova *)malloc(sizeof(Cprova)))!=NULL)
    {    
    memset(this,0,sizeof(Cprova));
    }
  return this;
  }
.........

Adesso è il momento di inizializzare gli elementi della nostra classe simulata. Alcuni sono a discrezione dello sviluppatore, come tutte le variabili, sia pubbliche che private dell'istanza; i puntatori alle funzioni metodo, invece devono essere assegnati tenendo conto del loro significato. Visto che tali puntatori sono in realtà variabili, e quindi possono essere assegnati come le variabili, occorre realizzare fisicamente il legame fra l'implementazione del metodo e la sua interfaccia. E questo si realizza con una banale assegnazione di puntatori.
Torniamo al nostro esempio:

#include <stdio.h>
#include "yedstd.h"
#include "yedprova.h"

// COSTRUTTORE DINAMICO OGGETTO DI TIPO Cprova
Cprova *_Cprova(void)
  {
  Cprova *this;

  if((this=(Cprova *)malloc(sizeof(Cprova)))!=NULL)
    {   
    memset(this,0,sizeof(Cprova));
    this->metodo=metodo;
    this->PRIVATE(foo_priv)=2;
    }
  return this;
  }

// DISTRUTTORE DINAMICO OGGETTO DI TIPO Cprova
*_DCprova(void *this)
  {
  free(this);
  }

int metodo(void *pVWork)
  {
  Cprova *this=pvWork;

  printf("Invocazione metodo: elemento interno [%d]\n",          this->PRIVATE(foo_priv));
  return 0;
  }

La linea evidenziata in blu è vitale: è grazie ad essa che l'oggetto può invocare il metodo , e senza di essa il metodo non potrà mai essere invocato. Occorre inserire nel costruttore della classe una assegnazione per ciascun metodo implementato, sempre che sia previsto nella interfaccia, per fare in modo che un oggetto possa invocare correttamente i propri metodi.
Il metodo _DCprova , il distruttore della nostra classe simulata, non fa altro che liberare l'area di memoria legata all'oggetto allocato dal costruttore. All'interno di questa funzione, lo sviluppatore può ( deve ) effettuare anche le altre operazioni legate ad un corretto e totale rilascio delle risorse utilizzate dal nostro oggetto, come ad esempio la liberazione di altre aree di memoria associate ad elementi private della classe.

 


Copyright (C) - Giulio A. - ynoxia(at)gmail(dot)com