New e delete
Neanche gli operatori new e delete
fanno eccezione, anche loro possono essere ridefiniti sia a livello di classe
o addirittura globalmente.
Sia come funzioni globali che come funzioni membro, la new riceve
un parametro di tipo size_t che al momento della chiamata e` automaticamente
inizializzato con il numero di byte da allocare e deve restituire sempre un
void* ; la delete invece riceve un void*
e non ritorna alcun risultato (va dichiarata void ). Anche se non
esplicitamente dichiarate, come funzioni membro i due operatori sono sempre
static .
Poiche` entrambi gli operatori hanno un prototipo predefinito, non e` possibile
avere piu` versioni overloaded di new e delete , e`
possibile averne al piu` una unica definizione globale e una sola definizione
per classe come funzione membro. Se una classe ridefinisce questi operatori
(o uno dei due) la funzione membro viene utilizzata al posto di quella globale
per gli oggetti di tale classe; quella globale definita (anch'essa eventualmente
ridefinita dall'utente) sara` utilizzata in tutti gli altri casi.
La ridefinizione di new e delete e` solitamente effettuata
in programmi che fanno massiccio uso dello heap al fine di evitarne una eccessiva
frammentazione e soprattutto per ridurre l'overhead globale introdotto dalle
singole chiamate.
Ecco un esempio di new e delete globali:
void* operator new(size_t Size) {
return malloc(Size);
}
void operator delete(void* Ptr) {
free(Ptr);
}
Le funzioni malloc() e free() richiedono al sistema
(rispettivamente) l'allocazione di un blocco di Size byte o la
sua deallocazione (in quest'ultimo caso non e` necessario indicare il numero
di byte).
Sia new che delete possono accettare un secondo parametro,
nel caso di new ha tipo void* e nel caso della delete
e` di tipo size_t : nella new il secondo parametro
serve per consentire una allocazione di un blocco di memoria ad un indirizzo
specifico (ad esempio per mappare in memoria un dispositivo hardware), mentre
nel caso della delete il suo compito e` di fornire la dimensione
del blocco da deallocare (utile in parecchi casi). Nel caso in cui lo si utilizzi,
e` compito del programmatore supplire un valore per il secondo parametro (in
effetti solo per il primo parametro della new e` il compilatore
che fornisce il valore).
Ecco un esempio di new che utilizza il secondo parametro:
void* operator new(size_t Size, void* Ptr = 0) {
if (Ptr) return Ptr;
return malloc(Size);
}
int main() {
// Supponiamo di voler mappare un certo
// dispositivo hardware tramite una istanza di un apposito tipo
const void* DeviceAddr = 0xA23;
// Si osservi il modo in cui viene fornito
// il secondo parametro della new
TMyDevice Unit1 = new(DeviceAddr) TMyDevice;
/* ... */
return 0;
}
Si noti che non c'e` una delete duale per questa forma di new
(perche` una delete non puo` sapere se e come e` stata allocato l'oggetto
da deallocare), questo vuol dire che gli oggetti allocati nel modo appena visto
(cioe` fornendo alla new un indirizzo) vanno deallocati con tecniche
diverse.
E` possibile sovraccaricare anche le versioni per array di questi operatori.
I prototipi di new[] e delete[] sono identici a quelli
gia` visti in particolare il valore che il compilatore fornisce come primo parametro
alla new[] e` ancora la dimensione complessiva del blocco da allocare.
Per terminare il discorso su questi operatori, bisogna accennare a cio` che
accade quando una allocazione non riesce (generalmente per mancanza di memoria).
In caso di fallimento della new , lo standard prevede che venga
chiamata una apposita funzione (detta new-handler) il cui comportamento
di default e` sollevare una eccezione di tipo std::bad_alloc che
bisogna intercettare per gestire il possibile fallimento.
E` possibile modificare tale comportamento definendo e istallando una nuova
new-handler. La generica new-handler deve essere una funzione
che non riceve alcun parametro e restituisce void , tale funzione
va installata tramite una chiamata a std::set_new_handler il cui
prototipo e` dato dalle seguenti definizioni:
typedef void (*new_handler)();
// new_handler e` un puntatore ad una funzione
// che non prende parametri e restituisce void
new_handler set_new_handler(new_handler HandlePtr);
La funzione set_new_handler riceve come parametro la funzione da
utilizzare quando la new fallisce e restituisce un puntatore alla
vecchia new-handler. Ecco un esempio di come utilizzare questo strumento:
void NoMemory() {
// cerr e` come cin, ma si usa per inviare
// messaggi di errore...
cerr << "Out of memory... Program aborted!" << endl;
abort();
}
int main(int, char* []) {
new_handler OldHandler = set_new_handler(NoMemory);
char* Ptr = new char[1000000000];
set_new_handler(OldHandler);
/* ... */
}
Il precedente esempio funziona perche` la funzione standard abort()
provoca la terminazione del programma, in realta` la new-handler viene
richiamata da new finche` l'operatore non e` in grado di restituire
un valore valido, per cui bisogna tenere conto di cio` quando si definisce una
routine per gestire i fallimenti di new .
|