C'è una differenza importante tra i due.
Tutto ciò che non è allocato newsi comporta in modo simile ai tipi di valore in C # (e le persone spesso dicono che quegli oggetti sono allocati nello stack, che è probabilmente il caso più comune / ovvio, ma non sempre vero. Più precisamente, gli oggetti allocati senza usare newhanno l'archiviazione automatica durata
Tutto l'allocato con newviene allocato sull'heap e viene restituito un puntatore ad esso, esattamente come i tipi di riferimento in C #.
Qualunque cosa allocata nello stack deve avere una dimensione costante, determinata in fase di compilazione (il compilatore deve impostare correttamente il puntatore dello stack o se l'oggetto è un membro di un'altra classe, deve regolare la dimensione di quell'altra classe) . Ecco perché le matrici in C # sono tipi di riferimento. Devono essere, perché con i tipi di riferimento, possiamo decidere in fase di runtime quanta memoria chiedere. E lo stesso vale qui. Solo le matrici con dimensione costante (una dimensione che può essere determinata in fase di compilazione) possono essere allocate con durata di memorizzazione automatica (in pila). Le matrici di dimensioni dinamiche devono essere allocate sull'heap, chiamandonew .
(Ed è qui che si interrompe qualsiasi somiglianza con C #)
Ora, qualsiasi cosa allocata nello stack ha una durata di archiviazione "automatica" (puoi effettivamente dichiarare una variabile come auto , ma questo è il valore predefinito se non viene specificato nessun altro tipo di archiviazione, quindi la parola chiave non è realmente utilizzata nella pratica, ma è qui che viene da)
La durata della memorizzazione automatica indica esattamente come suona, la durata della variabile viene gestita automaticamente. Al contrario, qualsiasi elemento allocato sull'heap deve essere eliminato manualmente dall'utente. Ecco un esempio:
void foo() {
bar b;
bar* b2 = new bar();
}
Questa funzione crea tre valori che vale la pena considerare:
Alla riga 1, dichiara una variabile bdi tipobar nello stack (durata automatica).
Alla riga 2, dichiara un barpuntatore b2sullo stack (durata automatica) e chiama nuovo, allocando abar oggetto sull'heap. (durata dinamica)
Quando la funzione ritorna, si verifica quanto segue: in primo luogo, non b2rientra nell'ambito di applicazione (l'ordine di distruzione è sempre opposto all'ordine di costruzione). Ma b2è solo un puntatore, quindi non succede nulla, la memoria che occupa viene semplicemente liberata. E, soprattutto, la memoria a cui punta (l' baristanza nell'heap) NON viene toccata. Viene liberato solo il puntatore, poiché solo il puntatore aveva una durata automatica. Secondo,b va al di fuori dell'ambito, quindi poiché ha una durata automatica, viene chiamato il suo distruttore e la memoria viene liberata.
E l' baristanza sul mucchio? Probabilmente è ancora lì. Nessuno si è preso la briga di cancellarlo, quindi abbiamo fatto trapelare memoria.
Da questo esempio, possiamo vedere che qualsiasi cosa con durata automatica è garantita per far chiamare il suo distruttore quando esce dal campo di applicazione. Questo è utile Ma tutto ciò che è allocato sull'heap dura finché ne abbiamo bisogno e può essere dimensionato dinamicamente, come nel caso degli array. Anche questo è utile. Possiamo usarlo per gestire le nostre allocazioni di memoria. E se la classe Foo allocasse un po 'di memoria sull'heap nel suo costruttore e cancellasse quella memoria nel suo distruttore. Quindi potremmo ottenere il meglio da entrambi i mondi, allocazioni di memoria sicure che sono garantite per essere nuovamente liberate, ma senza i limiti di forzare tutto per essere nello stack.
E questo è praticamente esattamente come funziona la maggior parte del codice C ++. Guarda le librerie standardstd::vector per esempio. In genere viene allocato nello stack, ma può essere ridimensionato e ridimensionato in modo dinamico. E lo fa allocando internamente la memoria sull'heap, se necessario. L'utente della classe non lo vede mai, quindi non c'è alcuna possibilità di perdere memoria o dimenticare di ripulire ciò che hai assegnato.
Questo principio si chiama RAII (Resource Acquisition is Initialization) e può essere esteso a qualsiasi risorsa che deve essere acquisita e rilasciata. (socket di rete, file, connessioni al database, blocchi di sincronizzazione). Tutti possono essere acquisiti nel costruttore e rilasciati nel distruttore, quindi sei sicuro che tutte le risorse acquisite verranno liberate di nuovo.
Come regola generale, non utilizzare mai new / delete direttamente dal codice di alto livello. Avvolgilo sempre in una classe in grado di gestire la memoria per te e che assicurerà che venga liberata di nuovo. (Sì, potrebbero esserci delle eccezioni a questa regola. In particolare, i puntatori intelligenti richiedono di chiamare newdirettamente e passare il puntatore al suo costruttore, che quindi prende il posto e garantisce che deletevenga chiamato correttamente. Ma questa è ancora una regola empirica molto importante )