allocazione e deallocazione di un vettore dinamico - TelPar

Transcript

allocazione e deallocazione di un vettore dinamico - TelPar
Appunti di OOP in C++
telpar.altervista.org
Allocazione dinamica della memoria: gli operatori new e delete
Gli oggetti dinamici sono quegli oggetti che vengono allocati nell'area heap. Il loro ciclo di vita è
deciso a tempo di esecuzione e non a tempo di compilazione, come, per esempio, le variabili
automatiche.
L'allocazione dinamica consente una migliore flessibilità ma a scapito delle prestazioni: un
overhead (sovraccarico) a tempo di esecuzione. Viceversa, l'allocazione statica è migliore come
prestazioni, ma assolutamente poco flessibile
Gli operatori new e delete consentono, rispettivamente, di allocare e deallocare oggetti nella
memoria heap ovvero consentono di allocare dinamicamente la memoria, per tutti i tipi di dato sia
quelli fondamentali che quelli definiti dall'utente e quindi anche oggetti di classi.
Sono versioni migliorate delle funzioni di libreria malloc() e free() del C.
L'operatore new crea automaticamente un oggetto allocandone la giusta dimensione in memoria e,
se tale allocazione va a buon fine, chiama il costruttore dell'oggetto restituendo un puntatore del
tipo corretto1.
Es.
int* p = new int;
viene allocato sull'heap lo spazio per contenere un int. P è un puntatore all'indirizzo di questo int
ovvero punta all'indirizzo iniziale più basso occupato dall'intero (generalmente 1 int occupa 4 byte).
Volendo è possibile anche fornire un inizializzatore per l'oggetto appena creato. Es.
int* p = new int (100); // allocazione di un oggetto di tipo 'int'
Inizializza un oggetto int appena creato è gli assegna il valore 100.
int* pCar = new Car (“Ferrari”); // all. di un ogg. di classe Car
Inizializza un oggetto di Classe (perché no...di Tipo) Car e lo inizializza al valore “Ferrari”.
Per liberare lo spazio allocato in memoria heap, si usa l'operatore delete:
delete p;
delete pCar;
Dealloca lo spazio in memoria puntato da p e da pCar; per l'oggetto di classe Car, invoca
preventivamente il distruttore dell'oggetto di cui si richiede l'eliminazione.
ALLOCAZIONE E DEALLOCAZIONE DI UN VETTORE DINAMICO
1 Quindi non c'è bisogno, come con malloc() di testare il puntatore di ritorno per verificare il successo dell'operazione
di allocazione della memoria.
Appunti di OOP in C++
telpar.altervista.org
Con new e delete possiamo creare vettori dinamici, che vengono allocati/deallocati sull'area heap,
invece di essere allocati a tempo di compilazione (o in area statica o in area stack).
Supponiamo di creare un vettore dinamico di 3 interi. La sintassi sarà la seguente:
int* p = new int [ 3 ];
Con questa istruzione e come se dicessimo: alloca adesso nell'heap uno spazio di memoria continua
atto a contenere 3 elementi di tipo int e memorizza in p il puntatore al primo elemento (ovvero
l'indirizzo iniziale del blocco di memoria dove è memorizzato il primo elemento) nel puntatore p.
Es. considerando che l'int viene rappresentato con 4 byte e che il primo elemento inizia dalla
locazione 1000 avremo:
[
1°int
] [
2°int
] [
3°int
]
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
[
2020
1000
]
...
2020 è l'indirizzo (iniziale) di memorizzazione del puntatore p, che conterrà, al termine
dell'operazione di new, il valore 1000.
Verrà allocato spazio giusto per contenere tre interi, ma il vettore ancora non contiente dati validi.
Per definirli, si procederà con la stessa sintassi di un vettore statico e cioè, un esempio:
p[ 0 ] = 1 ;
p[ 1 ] = 3 ;
p[ 2 ] = 5 ;
ATTENZIONE! Per deallocare l'intero vettore non è sufficiente invocare:
delete p;
Questa istruzione è corretta, ma il suo effetto è quello di rilasciare solo la memoria occupata dal
primo elemento ovvero la memoria puntata da p; i restanti 2 elementi rimarranno intatti in
memoria!
Per cancellare dalla memoria tutti i 3 elementi, la chiamata di delete dovrà allora assumere la
seguente sintassi:
delete [] p;
Appunti di OOP in C++
telpar.altervista.org
ALLOCAZIONE E DEALLOCAZIONE DI UN VETTORE DINAMICO DI
PUNTATORI
I puntatori sono delle variabili che contengono l'indirizzo di altri variabili. Dunque, nulla toglie che,
come tutte le variabili, possano essere organizzati anch'essi in dei vettori. Qui vedremo come creare
vettori dinamici (cioè allocati in area heap) di puntatori.
Un vettore di puntatori, in generale, sia esso statico o dinamico, ci può servire quando vogliamo
creare un certo numero di oggetti in memoria heap, non contigui, dello stesso tipo (ovvero della
stessa classe base).
Ciascun elemento di questo vettore conterrà l'indirizzo dell'oggetto restituito da new.
Ciò consente di gestirli comodamente con un ciclo for, sia per quanto riguarda la creazione,
definizione, il loro utilizzo, che per quanto riguarda la loro deallocazione.
Se poi il vettore di puntatori è anche esso dinamico (e non statico), come in questo caso, siamo in
grado di gestirne la dimensione e la vita a tempo di esecuzione rendendo il software ancora più
flessibile.
La creazione di un vettore dinamico di puntatori la possiamo vedere in due fasi:
- creazione con new di uno spazio di memoria contiguo, “vuoto”, in area heap atto a contenere un
vettore di puntatori (...il “telaio” con delle celle vuote che dovranno ospitare dei puntatori....)
- creazione con new, singolarmente, di ogni oggetto e assegnazione del puntatore (restituito da new)
al singolo elemento del vettore (...riempiamo le celle del “telaio” con dei puntatori...)
Es.
1. Creazione (sull'heap) del vettore “vuoto” :
ptr[]
2000
2004
2008
Appunti di OOP in C++
telpar.altervista.org
2. Caricamento del vettore con I puntatori restituiti dai new:
ptr[ 0 ] = new int( 2 );
ptr[ 1 ] = new int( 4 );
ptr[ 2 ] = new int( 6 );
new int( 2 );
new int( 4 );
new int( 6 );
pr
ptr[]
1200
1550
1580
2000
2004
2008
2
4
6
1200
1550
1580
Attenzione: le locazioni di ptr[] sono contigue, ma le locazioni contenute in ciascun elemento (i
puntatori), ovviamente, non devono essere obbligatoriamente contigue.
Dal momento che l'operatore new restituisce un puntatore al primo elemento di questo vettore ed
essendo ques'ultimo anch'esso un puntatore, avremo che la variabile che conterrà sarà un puntatore
a un puntatore del tipo dell'oggetto.
Vediamo subito la sintassi. Es.
int** ptr;
//[tipo][var] [int*][*ptr]....da cui int* *ptr
// ptr punta a un puntatore intero
// (cioè contiene l'indirizzo di una var. intera)
// quindi è un punt. a un puntatore (quindi due asterischi)
ptr = new int* [ 3 ]; //alloca sull'heap lo spazio per contenere 3 puntatori a int
// creo il telaio “vuoto” che riempirò con I punt. dati dai new:
ptr[ 0 ] = new int( 2 );
ptr[ 1 ] = new int( 4 );
ptr[ 2 ] = new int( 6 );
// riempiamo il vettore
// con gli indirizzi restituiti
// dai singoli new
// stampiamo 2, 4, 6
for ( int i=0; i<3; i++ )
cout << *ptr[ i ] << '\n';
// è necessaria la deferenziazione (sennò stampa gli
// indirizzi ovvero il contenuto della cella)
for ( int i=0; i<3; i++ )
delete ptr[ i ]; // 1)dealloca il singolo oggetto creato con new
delete [] ptr; // 2)dealloca il vettore di punt. (...il telaio...) creato con new
Appunti di OOP in C++
telpar.altervista.org
La deallocazione avviene in modo inverso alla allocazione.
Si deallocano prima tutte le aree di memoria create con new: i loro indirizzi si trovano nel vettore
ptr: il primo elemento di ptr, contiene l'indirizzo del primo oggetto creato con new e così via.
Lo si fa, comodamente, con un ciclo for che sintetizza queste istruzioni:
delete ptr[ 0 ]; // dealloca ciò che era stato allocato con new int( 2 )
delete ptr[ 1 ]; // dealloca ciò che era stato allocato con new int( 4 )
delete ptr[ 2 ]; // dealloca ciò che era stato allocato con new int( 6 )
Resta, infine, il vettore di puntatori (il telaio con le celle riempite) che viene deallocato come ogni
vettore di variabili scalari:
delete [] ptr;
OGGETTI E POLIMORFISMO
Nel caso che il vettore contenga dei puntatori alla classe base,
solo nel caso che nella classe base (che contiene il metodo virtuale invocato in fase di esecuzione)
sia stato definito un distruttore virtuale.
Dunque, le classi base che contengono un metodo virtuale devono sempre contenere un distruttore
virtuale.