Skip to content

Il Ciclo di Vita (Costruttori e Inizializzazione)

La creazione di un oggetto (Inizializzazione) e la sua distruzione compongono il Ciclo di Vita (Object Life Cycle).

Nel capitolo precedente abbiamo usato un metodo set_anno() per proteggere i dati. Tuttavia, la variabile originaria anno era ancora tecnicamente spazzatura prima della chiamata al metodo. Un Costruttore è un metodo speciale che viene richiamato automaticamente e inevitabilmente nel momento esatto in cui l’oggetto viene creato. L’obiettivo del costruttore è portare l’oggetto in uno stato iniziale coerente (class invariant).

Il costruttore non ha alcun tipo di ritorno e deve avere lo stesso identico nome della classe. Puoi crearne molteplici (sovrascrittura/overloading), a patto che accettino parametri diversi.

struct Orologio {
int anno;
// Costruttore 1: senza argomenti (imposta il default)
Orologio() {
anno = 2019;
}
// Costruttore 2: accetta un argomento
Orologio(int nuovo_anno) {
anno = nuovo_anno;
}
};

Il C++ fa distinzione tra l’inizializzazione di tipi primitivi/POD e le classi fully-featured. Per un POD o un int, l’inizializzazione può essere ambigua. Esistono storicamente quattro approcci validi per assegnare un valore o inizializzare a zero:

int a = 0; // Inizializzazione tramite uguaglianza (Equals initialization)
int b{}; // Braced initialization (Inizializzazione con graffe)
int c = {}; // "Equals-plus-braces" initialization
int d(0); // Inizializzazione tramite parentesi tonde

Di queste quattro opzioni, l’inizializzazione con parentesi tonde () per classi e primitive può rivelarsi traditrice, poiché collide fatalmente con le regole di parsing (lettura del testo sorgente) del linguaggio.

The “Most Vexing Parse” (L’Analisi Più Irritante)

Section titled “The “Most Vexing Parse” (L’Analisi Più Irritante)”

Immagina di voler chiamare il costruttore base di una classe Tassonomista che non accetta parametri. Potresti ingenuamente usare le tonde vuote (), così:

Tassonomista t8(); // Attenzione!!!

Invece di creare un oggetto, il compilatore legge questa riga come: “Dichiara l’esistenza di una funzione chiamata t8, che non accetta argomenti e restituisce un oggetto Tassonomista”. È la dichiarazione di una funzione! Questo fenomeno è noto universalmente come Most Vexing Parse.

Per rimediare a questo caos, i creatori del linguaggio hanno introdotto la Braced Initialization (Inizializzazione Uniforme) con parentesi graffe {}.

La Regola d’Oro per lo sviluppo C++ moderno è: Usa le parentesi graffe per inizializzare ovunque.

L’uso di {} non solo previene bug oscuri come le dichiarazioni involontarie di funzioni, ma previene anche le silenziose e pericolose Narrowing Conversions (Conversioni Restrittive).

Prima che l’oggetto muoia e venga spazzato via dalla memoria RAM, viene invocata un’ultima funzione di pulizia: il Distruttore. Viene usato per chiudere i file aperti, flushare le connessioni di rete o liberare memoria.

Si dichiara usando la tilde ~ davanti al nome della classe:

struct Terra {
// Il Distruttore (non può accettare argomenti)
~Terra() {
printf("Rilascio risorse planetarie...");
}
};

Il compilatore si occuperà di richiamare questa funzione automaticamente appena l’oggetto esce dallo scope vitale. Ne parleremo estesamente più avanti (“Tracing the Object Life Cycle”).