Newsgroup: it.comp.lang.c++
Titolo: Differenze nell'inizializzare una classe

Volevo sapere la differenza tra l'inizializzazione diretta di una classe
es NomeClasse MyClass
e l'inizializzazione attraverso la parola chiave new
es MyClass = new NomeClasse
Più in particolare quando è conveniente utilizzare una forma e quando
un'altra.
Grazie Bruno

es MyClass = new NomeClasse
Questi che chiami "inizializzazioni" sono in realtá due
concetti abbastanza diversi: il primo é una _dichiarazione_,
che anzitutto ha come scopo quello di "rendere noto" al
compilatore che l'identificatore "MyClass", in questo
scope (contesto lessicale), sta ad indicare un oggetto
di tipo NomeClasse; il secondo é una _istruzione_,
che indica una modifica per assegnamento di un oggetto
MyClass, che deve essere giá noto (e normalmente di
tipo puntatore-a-NomeClasse).
Forse quello che ti confonde é che spesso una
dichiarazione di una variabile svolge anche il ruolo di
_definizione_, cioé quello di dire al compilatore "questo
oggetto ``vive'' qui: inizializzalo"; e la costruzione new
chiede anche, esplicitamente, di inizializzare l'oggetto.
Per questo, immagino, parli di "inizializzazioni".  Ma
la _inizializzazione_, se avviene, é esattamente identica
nei due casi; quello che cambia é "il resto", ció che
NON ha a vedere con la inizializzazione ma solo con
le sue "condizioni al contorno"... DOVE si trova la
memoria occupata dall'oggetto, QUANDO la sua
inizializzazione (ovvero chiamata del costruttore)
avviene.  In altri termini, la "lifetime" dell'oggetto.
Un costrutto del tipo:
    NomeClasse MyClass;
se é una definizione oltre che una dichiarazione,
puó, a seconda del suo contesto, indicare diversi
lifetime (e "residenze"):
-- se é fuori da ogni blocco ("globale"), e/o adornato
    dall'aggettivo "static", l'oggetto vive "in memoria
    dati statica"; l'inizializzazione, é garantito, avviene
    prima di qualsiasi esecuzione di codice seguente
    nello stesso blocco (se entro un blocco) -- prima
    di qualsiasi esecuzione di codice dello stesso file
    sorgente, se globale static -- prima dell'esecuzione
    di main(), se globale e non static (vi sono inoltre
    alcuni altri vincoli su quando il compilatore puó
    scegliere di eseguire l'inizializzazione);
-- in tutti questi casi, la terminazione dell'oggetto
    avverrá prima della fine del programma, ma non
    prima dell'uscita da main() [o della chiamata ad
    exit(), ecc, se é cosí che il programma finisce]
-- se é entro un blocco, l'inizializzazione avviene
    dopo tutto il codice precedente e prima di tutto
    il codice seguente; la terminazione avviene
    "durante" l'uscita dal blocco, e se un blocco ha
    molteplici variabili, esse vengono distrutte in
    ordine inverso a quello di costruzione;
-- in questi casi, l'oggetto vive "nello stack" (lo si
    puó anche indicare esplicitamente usando per
    la dichiarazione l'aggettivo "auto", che non ha
    effetto, in quanto é, esattamente, il default).
Invece, data una dichiarazione del tipo
    NomeClasse *pMyClass;
se a un certo punto si trova l'istruzione di
assegnazione:
    pMyClass = new NomeClasse();
-- viene allocata (tramite l'opportuno caso di
    operator new) memoria nella "heap"
    (cosiddetta "allocazione dinamica"), e
    l'oggetto "vive" in questo spazio di heap;
-- l'oggetto non ha "terminazione" implicita,
    bensí deve esplicitamente essere chiesta
    a un certo punto con una istruzione del
    tipo:
        delete pMyClass;
    (omettere questa terminazione é un errore
    e porta i cosiddetti "resource leak"...).
(Cruciale, e spesso dimenticato: se il delete
avviene su di un puntatore che NON é alla
stessa identica classe nominata in new, ma
ad esempio su di una sua base, occorre che
detta base abbia un distruttore virtuale; se no,
si tratta di un grave errore, che normalmente
il compilatore non é in grado di diagnosticare).
Spero di avere illustrato a sufficienza le
differenze, comunque, per riassumere:
-- l'uso di new richiede allocazione dinamica
    e non comporta terminazione automatica
Quando non é richiesta l'allocazione di tipo
dinamico, e la terminazione automatica é
accettabile, questo presenta vari vantaggi:
le prestazioni sono assai superiori, e non é
possibile fare l'errore che porta "resource
leak".  Tuttavia, in vari casi, la flessibilitá
dell'allocazione dinamica é necessaria.  Ad
esempio, se devo creare un oggetto in una
funzione e restituirlo al chiamante, puó non
essere accettabile ritornarlo per valore, che
implica una copia e la conoscenza del tipo
esatto (nessun polimorfismo): spesso devo
in questo caso utilizzare new (e quel che
ritorno sará un puntatore).
L'allocazione dinamica é egualmente necessaria
se non so a priori (al momento di compilare)
esattamente quanto spazio di memoria serva,
ma questo, normalmente, in C++ Standard, non
é un problema frequente, poiché di solito la
soluzione giusta a questo problema é usare
un contenitore standard (vector, list, ...), che
"cresce" man mano che serve (internamente
esso userá allocazione dinamica, ma ció é
comodamente reso trasparente all'utente).
Nondimeno, l'allocazione dinamica resterá
necessaria se le copie degli oggetti hanno per
qualche ragione un costo proibitivo, ovvero se
il tipo di tutti gli oggetti del contenitore non é
noto a priori e per tutti identico (polimorfismo,
ancora una volta).
In pratica, un buon programmatore C++ si trova
raramente davanti ad un "dilemma" per scegliere
se usare una variabile locale (in stack, auto, senza
new) o un puntatore con new e delete: se si puó
usare la variabile locale (uso localizzato al blocco
e funzioni che esso chiama, ovvero copia di costo
accettabile e tipo noto a tempo di compilazione),
questo é generalmente piuttosto lampante, e, se é
possibile, é preferibile; se no, si ricorre al "new"
(eventualmente con utili supporti, tipo il template
auto_ptr<> che la libreria Standard C++ fornisce).
Alex









