stack (è "lo" stack per antonomasia).heap ("lo" heap per
antonomasia).
Gestire l'immagazzinamento di oggetti e dati sia nello stack che nello heap è costoso, e richiede cautela. Esistono vari accorgimenti, anche intrinseci del linguaggio, per evitare operazioni inutili ed assicurarsi di non sprecare spazio.
Il C++ ha esteso il significato di operatori già definiti nel C:
& (ampersand),
che consente di conoscere l'indirizzo di memoria dove è presente un
dato, una variabile, un oggetto, ottenendo così una
forma di puntatore a quel dato.* (star), che esegue l'operazione
opposta: dato un indirizzo di memoria ed un tipo di dati associato
a quell'indirizzo, è in grado di accedere alla memoria, ed
interpretarne il contenuto secondo il tipo corrispondente. -> (right-arrow), che
consente di passare dal puntatore al membro di un tipo strutturato
o di una classe (vale l'equivalenza fra (*a).b e
a->b).
,
dove si mostra la possibilità di definire e trattare puntatori a qualsiasi
oggetto come un vero e proprio tipo di dati. Una variabile di tipo
puntatore può quindi
non essere inizializzata a nulla, o contenere valori indefiniti,
oppure puntare ad aree di memoria ricavate dello heap mediante
un'operazione di allocazione, che può fallire.
Il C++ ha esteso il significato di questi operatori, affiancando, al
tipo di dati "puntatore a oggetto" un tipo di dati "riferimento (reference)
a oggetto". Si può così trasferire l'accesso
a oggetti sicuramente esistenti fra funzioni senza generare nello
stack copie di oggetti (con le relative creazioni e distruzioni)
indesiderate e probabilmente non necessarie. L'uso di oggetti di cui
sia disponibile una reference è identico a quelli generati
localmente. La differenza fra il passaggio di oggetti alle funzioni
per valore e per reference è illustrata da questo
La funzione chiamante ignora se una determinata funzione chiamata
acceda ad un argomento utilizzando una copia del valore oppure utilizzando
una reference.
L'unico elemento che permette alla funzione chiamante di essere
ragionevolmente certa
che un oggetto non venga modificato dalla funzione chiamata
è che l'argomento sia dichiarato const nel prototipo.
Come per ogni limitazione in C++, esistono costrutti che permettono di
superare anche la dichiarazione di variabili costanti (come ad esempio
const_cast<>) in casi
speciali. Bisogna tuttavia assolutamente evitare l'utilizzo di
tali costrutti in una funzione che dichiara argomenti costanti.
Un altro caso di operazione che può fallire e per la quale è necessario
utilizzare un puntatore è illustrato in questo
Dovrebbe ora risultare più chiara anche la ragione
per cui si utilizza l'operatore * per accedere al valore puntato
dagli iteratori della STL.
Come detto, nonostante le capacità del C++ di allocare oggetti sullo
stack siano piuttosto evolute (e spesso sufficienti), in alcuni
casi può essere necessario disporre di aree di memoria persistenti rispetto
al cambio di contesto. Oggetti dinamici di
questo tipo si possono ottenere utilizzando l'operatore new:
// A measurement that needs to survive this function or this context:
measurement *long_lived_meas =
new measurement(3.141516, "31 Dec 1999", "Mino Cancelli");
L'unico vero problema degli oggetti ottenuti dallo heap è proprio
la loro lunga durata: persistono finché non vengono distrutti
(utilizzando l'operatore delete). Inoltre, anche
in caso di termine del programma e del processo che lo esegue
non viene invocato il relativo distruttore
(cosa che può avere conseguenze indesiderate).
La dimenticanza di deallocare un oggetto allocato sullo heap
può rendere la corrispondente quantità di memoria
non più utilizzabile per la durata del processo. Questo fenomeno
è descritto come memory leak.
L'uso indiscriminato di oggetti allocati sullo heap è una pratica
da non incoraggiare. Se non si riesce a fare a meno di utilizzarli,
si può fare in modo di non dover mai invocare esplicitamente
l'operatore delete.
Le tecniche dei template e dell'overloading degli operatori sono state utilizzate per realizzare altri tipi di oggetti che si comportano come puntatori, ma hanno la caratteristica di invocare automaticamente la distruzione dell'oggetto puntato quando i vari contesti in cui viene utilizzato vengono chiusi. E' possibile così realizzare in modo sicuro:
Sono stati dunque sviluppati, da principio nel progetto BOOST (formato inizialmente da membri del comitato di standardizzazione del C++), e quindi integrati nello standard C++ 2011, due tipi di puntatori furbi:
#include <memory>Esempio
std::unique_ptr<measurement> meas(new measurement(3.141516, "31 Dec 1999", "Mino Cancelli"));
#include <memory>Esempio
std::shared_ptr<measurement> meas(new measurement(3.141516, "31 Dec 1999", "Mino Cancelli"));
Questi smart pointer possono essere utilizzati per rispondere
a qualsiasi
necessità di deallocazione degli oggetti che richiederebbe l'uso
esplicito di delete. Considerati i cospicui danni recati
all'umanità dalle memory leak nei primi anni di esistenza ed
impiego del C++, questa pratica è ora fortemente raccomandabile.
Nota: nello standard precedente del C++ (1998) era definito un solo tipo di
smart pointer, std::auto_ptr, che però richiedeva
l'assegnazione manuale del controllo del puntatore in caso di
condivisione o passaggio ad altre funzioni. Queste limitazioni pratiche
sono superate, come appena visto, nella nuova versione dello standard.
Esercizio: Modificare il programma che disegna le sfere tridimensionali in modo che legga i dati di un numero arbitrario di sfere da un file di testo.