In primo luogo, ricordare che gli oggetti in C++ possono essere creato nello stack o nell'heap.
Uno stack frame (o scope) è definito da un'istruzione. Può essere grande quanto una funzione o piccolo come un blocco di controllo del flusso (while
/if
/for
ecc.). Una coppia arbitraria {}
che racchiude un blocco arbitrario di codice costituisce anche una cornice di stack. Qualsiasi variabile locale definita all'interno di un frame andrà fuori campo una volta che il programma sarà uscito da quel frame. Quando una variabile stack esce dall'ambito, viene chiamato il suo distruttore.
ecco un classico esempio di uno stack frame (a esecuzione di una funzione) e una variabile locale dichiarata all'interno di esso, che uscirà di portata, una volta uscite pila telaio - una volta che la funzione termina:
void bigSideEffectGuy() {
BigHeavyObject b (200);
b.doSomeBigHeavyStuff();
}
bigSideEffectGuy();
// a BigHeavyObject called b was created during the call,
// and it went out of scope after the call finished.
// The destructor ~BigHeavyObject() was called when that happened.
Ecco un esempio in cui si vede uno stack frame essendo appena il corpo di un if
dichiarazione:
if (myCondition) {
Circle c (20);
c.draw();
}
// c is now out of scope
// The destructor ~Circle() has been called
l'unico modo per un oggetto stack creato a "rimangono in scope" dopo il telaio si esce è se è il valore di ritorno di una funzione. Ma questo non è veramente "rimanendo nell'ambito" perché l'oggetto è stato copiato. Quindi l'originale va fuori portata, ma viene fatta una copia. Esempio:
Circle myFunc() {
Circle c (20);
return c;
}
// The original c went out of scope.
// But, the object was copied back to another
// scope (the previous stack frame) as a return value.
// No destructor was called.
Ora, un oggetto può anche essere dichiarato nello heap. Per il gusto di questa discussione, pensa all'heap come a un amorfo frammento di memoria. A differenza dello stack, che assegna e deseleziona automaticamente la memoria necessaria mentre si entra e si esce dai frame dello stack, è necessario riservare manualmente e liberare memoria heap.
Un oggetto dichiarato nell'heap, dopo una moda, "sopravvive" tra i frame dello stack. Si potrebbe dire che un oggetto dichiarato sull'heap non va mai fuori portata, ma questo è dovuto al fatto che l'oggetto non è mai realmente associato ad alcun ambito. Tale oggetto deve essere creato tramite la parola chiave new
e deve essere definito da un puntatore.
È responsabilità dell'utente liberare l'oggetto heap una volta terminato. Si liberano oggetti heap con la parola chiave delete
. Il distruttore su un oggetto heap non viene chiamato finché non si libera l'oggetto.
I puntatori che fanno riferimento a oggetti heap sono di solito variabili locali associate agli ambiti. Una volta terminato di utilizzare l'oggetto heap, si consente ai puntatori che fanno riferimento ad esso di uscire dall'ambito. Se non si è liberato in modo esplicito l'oggetto puntato dal puntatore, il blocco della memoria heap non verrà mai liberato fino alla chiusura del processo (chiamata perdita di memoria).
Pensa a tutto questo: un oggetto creato in pila è come un palloncino attaccato a una sedia in una stanza. Quando esci dalla stanza, il fumetto si apre automaticamente. Un oggetto creato sull'heap è come un palloncino su un nastro, legato a una sedia in una stanza. Il nastro è il puntatore. Quando esci dalla stanza, il nastro scompare automaticamente, ma il pallone galleggia sul soffitto e occupa spazio. La procedura corretta è far scoppiare il palloncino con uno spillo, quindi uscire dalla stanza, dopodiché il nastro scomparirà.Ma, la cosa buona del palloncino sulla corda è che puoi anche slegare il nastro, tenerlo in mano e uscire dalla stanza e portare il palloncino con te.
Quindi, per andare all'esempio dell'elenco collegato: in genere, i nodi di tale elenco vengono dichiarati nell'heap, con ogni nodo che contiene un puntatore al nodo successivo. Tutto ciò è in bilico e non va mai fuori portata. L'unica cosa che potrebbe uscire dall'ambito è il puntatore che punta alla radice della lista - il puntatore che usi per fare riferimento alla lista in primo luogo. Questo può andare fuori portata.
Ecco un esempio di creazione di roba sul mucchio, e il puntatore radice uscire di portata:
if (myCondition) {
Node* list_1 = new Node (3);
Node* list_2 = new Node (4);
Node* list_3 = new Node (5);
list_1->next = list_2;
list_2->next = list_3;
list_3->next = null;
}
// The list still exists
// However list_1 just went out of scope
// So the list is "marooned" as a memory leak
vostre domande aggiornamento è molto buona, e la risposta è no. Quando un puntatore esce dall'ambito, il distruttore dell'oggetto a cui sta puntando è ** non ** chiamato automaticamente. Devi chiamarlo tu stesso, e questo succede quando chiami 'delete' sul nodo per liberare la memoria. Questa è una buona cosa da tenere a mente quando si ha una matrice o un elenco di puntatori ad oggetti con implementazioni significative del distruttore. Fortunatamente, dato che devi comunque "liberare" te stesso, innescherai i distruttori. –
A proposito, se effettivamente hai bisogno di un elenco collegato per qualcosa, stai molto meglio usando std :: list rispetto al tuo elenco personalizzato collegato. – DSimon
Avrei dovuto dire 'delete' invece di' free'. Ignora 'gratis', è un retaggio di C. Usa solo 'new' e' delete' per l'allocazione/eliminazione dell'heap. –