# Dal C al C++/Classi isolate

Dal C al C++
Copertina Tutti i moduli · Sviluppo
* IntroduzioneDal C al C++/Introduzione
** I destinatari del libroDal C al C++/Introduzione#I destinatari del libro
** La struttura del libroDal C al C++/Introduzione#La struttura del libro
** Impieghi del linguaggioDal C al C++/Introduzione#Impieghi del linguaggio
** PanoramicaDal C al C++/Introduzione#Panoramica
* Utilizzo basilare di librerie
** ArrayDal C al C++/Array
** Il tipo "bool"Dal C al C++/Utilizzo basilare di librerie/Il tipo "bool"
** L'uso di classi e oggettiDal C al C++/Utilizzo basilare di librerie/L'uso di classi e oggetti
** Dichiarazioni e definizioniDal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni
*** Le variabili riferimentoDal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni#Le variabili riferimento
*** Il modificatore "const"Dal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni#Il modificatore "const"
*** Il sovraccaricamentoDal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni#Il sovraccaricamento
*** L'inizializzazione degli oggettiDal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni#L'inizializzazione degli oggetti
*** L'allocazione degli oggettiDal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni#L'allocazione degli oggetti
** L'uso dei templateDal C al C++/Utilizzo basilare di librerie/L'uso dei template
** L'uso dei namespaceDal C al C++/Utilizzo basilare di librerie/L'uso dei namespace
** I file di intestazioneDal C al C++/Utilizzo basilare di librerie/I file di intestazione
** L'uso delle stringheDal C al C++/Utilizzo basilare di librerie/L'uso delle stringhe
** I canali di ingresso/uscitaDal C al C++/Utilizzo basilare di librerie/I canali di ingresso/uscita
** La gestione delle eccezioni
** I contenitori standardDal C al C++/Utilizzo basilare di librerie/I contenitori standard
** Le funzioni standardDal C al C++/Utilizzo basilare di librerie/Le funzioni standard
* Utilizzo avanzato di librerie
** Ridefinizione di funzioni membroDal C al C++/Utilizzo avanzato di librerie/Ridefinizione di funzioni membro
** I parametri di defaultDal C al C++/Utilizzo avanzato di librerie/I parametri di default
** Gli iteratori sui contenitori
** Gli algoritmi sui contenitori
* Creazione di librerie
** Classi isolateDal C al C++/Classi isolate
*** I costruttoriDal C al C++/Classi isolate#I costruttori
*** I distruttoriDal C al C++/Classi isolate#I distruttori
*** Separazione tra interfaccia e implementazioneDal C al C++/Classi isolate#Separazione tra interfaccia e implementazione
** Gerarchie di classiDal C al C++/Gerarchie di classi
** Proprietari di risorse
** Eccezioni
** Namespace
** Template di funzione
** Template di classe
** Algoritmi su sequenze
* La metaprogrammazione
* La progettazione del software
* Gioco life di ConwayDal C al C++/Gioco life di Conway
Modifica il sommario

## I costruttori

Le seguenti istruzioni dichiarano alcune variabili:
Trattandosi di variabili di tipo valore, cioè né puntatori né riferimenti, ad esse corrisponde un oggetto del tipo indicato alla loro sinistra. Per ognuno dei quattro tipi presentati, vengono dichiarate due variabili, delle quali una non è inizializzata esplicitamente, e l'altra sì.
La variabile "a1" essendo di un tipo fondamentale, non viene affatto inizializzata. Tutte le altre variabili sono invece inizializzate, in quanto in assenza di un inizializzatore esplicito ne viene eseguito uno implicito.
Tali tipi non fondamentali, per quanto facenti parte della libreria standard, non fanno parte del linguaggio e sono quindi implementati usando il linguaggio stesso.
Qui vedremo, per gradi, come è possibile realizzare una classe che abbia tale comportamento.
Proviamo, per prendere un caso concreto, a realizzare una classe che implementi il tipo "complex<double>", rinunciando alla genericità del template, e chiamiamo "Complex" tale classe.
Un primo abbozzo è il seguente:
Possiamo dichiarare un oggetto di tale classe nel seguente modo:
Tuttavia, il nostro oggetto non sarà inizializzato. Per inizializzare automaticamente un oggetto, si deve definire una funzione membro che il compilatore chiami automaticamente alla creazione dell'oggetto. Tale funzione membro viene detta essere un "costruttore".
Per far sapere al compilatore che tale funzione è un costruttore, basta assegnare a tale funzione lo stesso nome della classe, come nel seguente codice:
Quindi, non si può dare a una funzione membro lo stesso nome della classe, se non per indicare che si tratta di un costruttore. A questo punto il nostro oggetto è automaticamente inizializzato con entrambe le variabili membro poste a zero.
Ora vorremmo che funzionasse anche la seguente inizializzazione esplicita:
A tale scopo, è necessario dichiarare un costruttore che accetti due parametri, come nel seguente codice:
Questo codice è corretto, in quanto si possono definire più costruttori sovraccaricati, purché, come tutte le funzioni sovraccaricate, differiscano per il tipo dei parametri. Tuttavia è anche possibile, e più compatto, usare i valori di default per i parametri, come nel seguente codice:

## I distruttori

Visto il successo della creazione di una classe per i numeri complessi, proviamo a creare una classe per le stringhe, che chiamiamo "String".
Il costruttore riceve un puntatore a carattere che si assume punti all'inizio di una stringa del C, cioè a un array di caratteri che termina con uno zero binario. Se si costruisce un oggetto di tipo "String" non inizializzato esplicitamente, il costruttore viene chiamato comunque, ricevendo il valore di default del parametro, che è una stringa vuota. Quindi l'oggetto viene sempre correttamente inizializzato.
L'oggetto contiene tre variabili membro:
* "array_", che è un puntatore a un array di caratteri allocato dinamicamente, e che contiene i caratteri della stringa, compreso il terminatore di stringa.
* "capacita_", che indica la lunghezza dell'array di caratteri.
* "lunghezza_", che indica il numero di caratteri effettivamente validi nell'array di caratteri.
L'oggetto sembra funzionare correttamente, ma ha un grosso problema: la memoria dinamica allocata da "new" nel costruttore non viene mai rilasciata. Si tratta di un cosiddetto "memory leak", cioè di una "perdita di memoria".
Il problema è dovuto al fatto che nel costruttore è stata allocata una risorsa, nella fattispecie un array di caratteri, e non c'è nessuna istruzione che la dealloca. Per fortuna, il linguaggio C++, come si occupa di richiamare automaticamente un costruttore alla creazione di un oggetto, così alla distruzione di un oggetto si occupa di richiamare automaticamente un'apposita funzione membro, detta "distruttore". Tutte le risorse allocate nel costruttore devono essere deallocate nel distruttore.
Mentre ci possono essere più costruttori, il distruttore, se presente, è unico, e non riceve parametri. Viene identificato dal fatto che il suo nome è uguale a quello della classe (come per i costruttori), e dal fatto che il suo nome è preceduto dal carattere "~".
Ecco un programma completo che usa costruttore e distruttore:
Il programma stampa "[abcd][XYZ]".
La variabile "s1" non è inizializzata esplicitamente, ma il suo costruttore viene chiamato comunque, senza parametri; quindi viene usato il valore di default dell'unico parametro.
Le variabili "s2" e "s3" sono inizializzate esplicitamente, con due sintassi diverse, ma con la stessa semantica, e quindi il loro costruttore viene chiamato allo stesso modo.
È stata aggiunta anche la funzione "c_str" per rendere l'uso della classe identico a quello della classe standard "string". Tale funzione è "const", in quanto la chiamata a tale funzione non modifica l'oggetto di tipo "String" a cui si applica, e rende un puntatore "const char", in quanto non si vuole consentire la modifica dell'array interno tramite questo puntatore.

## Separazione tra interfaccia e implementazione

Un programma di dimensioni non banali in linguaggio C è composto da più unità di compilazione, che sono i file che tipicamente hanno suffisso ".c", e da più file di intestazione applicativi, che tipicamente hanno suffisso ".h". Nei file di intestazione si pongono le dichiarazioni delle variabili, costanti, funzioni, e tipi globali. Nelle unità di compilazione si pongono le definizioni delle variabili e funzioni globali, nonché dichiarazioni e definizioni di variabili, costanti, funzioni, e tipi locali al modulo.
In C++ tutto questo viene conservato, ma solitamente si usa un'estensione diversa per le unità di compilazione. L'estensione più usata è ".cpp" (per "C plus plus"), anche se alcuni usano ".cxx" ("C++" con i "più" ruotati), o ".cc", o ".C" con la lettera maiuscola. Per i file di intestazione l'estensione è più usata è ".h", come in C, ma alcuni usano ".hpp" o ".hxx".
In una classe una funzione membro può essere definita o anche soltanto dichiarata, cioè si può inserire solo quello che in C si chiama prototipo di funzione.
Per separare la definizione dalla dichiarazione, si procede come nel seguente esempio, in cui si suppone di avere tre file:
Per generare un eseguibile, bisogna compilare i file "main.cpp" e "c.cpp", e poi linkarli insieme. Il programma stampa "0 1".
Il file "c.h" contiene le cosiddette "guardie di compilazione" che sono le direttive per il preprocessore "#ifndef", "#define" e "#endif". Servono a evitare che il tale file sia letto più volte.
Infatti, per ogni unità di compilazione, ogni definizione di classe deve essere incontrata una sola volta dal compilatore. Provando a togliere tali direttive, in realtà il programma è ancora corretto, ma basta duplicare una direttiva di inclusione del file di intestazione, che il compilatore si lamenta che "struct C" è definito più volte. Ovviamente, tutti i programmatori evitano di includere un file di intestazione più volte nella stessa unità di compilazione, ma siccome gli stessi file di intestazione spesso includono altri file di intestazione, è facile giungere a inclusioni multiple.
Nella definizione di "struct C", le funzioni "f" e "g" hanno solo la dichiarazione, come nei prototipi di funzione. Questo è sufficiente a compilare il file "main.cpp", ma, se non esistesse il file "c.cpp", il linker lamenterebbe l'assenza della definizione della funzione "f", vista come membro della classe "C", in quanto tale funzione viene chiamata dalla funzione "main". Per la funzione "g" non è necessaria nessuna definizione, in quanto non è mai utilizzata.
Il file "c.cpp" include il file "c.h" e poi definisce il corpo della funzione membro "f".
Siccome qui siamo all'esterno della classe, definire semplicemente una funzione "f" non sarebbe una corretta definizione di funzione membro, in quanto rimarrebbe indeterminata la classe a cui appartiene tale funzione. Pertanto, si usa l'espressione "C::f", in cui il nome della funzione è qualificato dal nome della classe. I due caratteri di "due punti" costituiscono un unico operatore, detto "operatore di risoluzione di ambito" (in inglese, "scope resolution operator").
Sarebbe stato errato scrivere "C: :f", mentre "C :: f" sarebbe stato corretto.
L'operatore "::", che non esiste in linguaggio C, serve a indicare, scrivendolo alla sua sinistra, in quale "struct", "classe" o "namespace" va ricercato l'identificatore citato alla sua destra.