2013-01-19 5 views
5

sto guardando il seguente pezzo di codice lista collegata ho trovato online:C++ - Perché impostare l'oggetto su null dopo l'eliminazione?

void DeleteAfter(Node **head){ 
     if(*head==NULL){ 
      return; 
     }else{ 
      Node *temp = NULL; 
      temp = (*head)->next; 
      (*head)->next = (*head)->next->next; 
      delete temp; 
      temp=NULL; 
     } 
} 

Io non sono che abile con C++, quindi questo potrebbe essere una domanda cattiva, ma perché temperatura viene impostata su null dopo essere stato cancellato? È un passo necessario?

+1

No. Basta usare un puntatore intelligente. – chris

+3

qui è assolutamente irrilevante se lo si imposta su NULL o no. 'temp' è una variabile con memorizzazione automatica, il che significa che uscirà dall'ambito dopo l'uscita dal blocco' else'. ma come dice @chris, basta usare i puntatori intelligenti –

+2

anche, il fatto che '* head' non sia' NULL' non significa che '(* head) -> next' non sia' NULL', e tu stia cercando di dereferenziare quel puntatore ('(* head) -> next -> ...') –

risposta

7

Non è necessario. Alcune persone hanno l'abitudine di farlo anche quando non ha alcun risultato. Un aggressivo ottimizzatore del compilatore eliminerà questo codice, quindi in realtà non fa alcun danno. Ma avrei scritto:

void DeleteAfter(Node *head) { 
    if (head) { 
    Node *next = head->next; 
    if (next) { 
     head->next = next->next; 
     delete next; 
    } 
    } 
} 

Nota ho eliminato un livello di indirezione inutile e ha aggiunto un controllo per assicurarsi che ci sia un "nodo dopo" per eliminare.

Il razionale per l'abitudine è che se un puntatore sempre si riferisce a un oggetto valido o è nullo, è possibile fare affidamento su assegni null come equivalenti ai controlli di validità.

Per questo motivo, Ada, un linguaggio spesso utilizzato in sistemi di sicurezza critici, inizializza i puntatori a null e definisce l'operatore equivalente delete per impostare automaticamente l'argomento null. Il tuo C++ sta simulando questo comportamento.

In pratica, il valore di questa disciplina non è quello che si spera. Una volta tanto tempo impedisce un errore stupido. Una cosa carina, tuttavia, è che le visualizzazioni del debugger del contenuto del puntatore hanno senso.

+0

Ricorda che head-> next potrebbe essere NULL causando l'arresto di questo codice su head-> next = head-> next-> next; – drescherjm

+0

Così vero ... ho aggiornato per tener conto di ciò. Non ha senso controllare il nodo principale e non capo-> successivo. – Gene

0

Non è necessario, e alcuni (incluso me) considerano una cattiva pratica.

La motivazione per impostarlo su NULL è che è possibile verificare in seguito se è stato eliminato e accedervi altrimenti se non lo fosse. Inoltre, ciò impedirebbe una doppia eliminazione, poiché delete su un puntatore NULL è un non operante.

D'altra parte, può nascondere i bug. Se l'oggetto è stato cancellato, non ha senso utilizzarlo, giusto? È sapere che l'oggetto è stato eliminato, non fare affidamento su un assegno.

Per esempio

if (p != NULL) //or just if (p) 
    p->doStuff() 

Perché? Non sai già se è stato cancellato o no? Non è la pulizia parte della logica?

1

Non è necessario impostare la variabile del puntatore locale su NULL dopo averlo eliminato. È necessario impostare i puntatori su NULL se si desidera riutilizzare il puntatore, dopo aver verificato NULL, è possibile assegnare un nuovo indirizzo ad esso. Normalmente lo facciamo per variabili del membro del puntatore e variabili globali del puntatore.

1

Se temp era una variabile globale o membro, quindi l'impostazione su NULL non è una cattiva idea.

Ho preso l'abitudine di impostare i puntatori su NULL dopo aver usato un garbage collector conservatore con codice C. Non avendo indicazioni su memoria inutilizzata è come trova la spazzatura da raccogliere. Ma in questo caso si dovrebbe anche fare

temp->next = NULL; 
1

Se la variabile temp potrebbe essere riutilizzati in seguito nel codice, allora è buona norma impostare a NULL.

Ci sono due motivi per cui normalmente si imposta un puntatore su NULL dopo averlo rilasciato.

1.) Una volta rilasciato un puntatore, la memoria all'indirizzo indicato non è più disponibile per il programma. In teoria, quella memoria potrebbe ora essere utilizzata da qualsiasi altro programma, incluso il sistema operativo stesso! Tentativo di rilasciare un puntatore che è già stato rilasciato e quindi punta a chissà cosa potrebbe causare un grosso problema. Fortunatamente i sistemi operativi moderni proteggono da questo, ma il programma si bloccherà ancora con un errore di accesso illegale. Rilasciando un puntatore nullo OTOH non farà assolutamente nulla.

2.) Si deve sempre controllare che un puntatore non sia NULL prima di de-referenziarlo con l'operatore *. La de-referenziazione di un puntatore NULL causerà un errore in fase di esecuzione. De-referenziare un puntatore rilasciato che punta a una memoria arbitraria è anche peggio. Un puntatore rilasciato deve sempre essere impostato su NULL in modo che il codice successivo possa assumere un puntatore non nullo per i dati validi. Altrimenti non c'è modo di sapere se il puntatore è ancora valido.

Come per la domanda originale, la variabile di puntatore temp viene dichiarata come variabile locale in una funzione breve in cui non viene mai più utilizzata. In questo caso non è necessario impostarlo su NULL poiché non è più disponibile dopo il ritorno della funzione.

Tuttavia, la linea ...

(*head)->next = (*head)->next->next; 

non riesce a fa in modo (*head)->next non è nullo prima di tentare di de-riferimento, un no-no.

Una versione migliore sarebbe ...

int DeleteAfter(Node **head){ 
    Node *node_after = NULL; 

    if(*head==NULL) 
    return -1; 

    node_after = (*head)->next; 

    if(node_after == NULL) 
    return -1; 

    (*head)->next = node_after->next; 
    delete node_after; 

    return 0; 
    } 

Ora la persona che utilizza la funzione può verificare se l'eliminazione del nodo ha avuto successo per il valore di ritorno e non v'è alcun rischio di cercare di eliminare un inesistente nodo.

0

Nell'esempio di codice non vi è alcun evidente beneficio immediato, tuttavia è possibile che vi sia un vantaggio in termini di costi di manutenzione a lungo termine. L'idea è che qualcuno possa eventualmente aggiungere il codice dopo la cancellazione di temp che tenta di dereferenziare temp. Ciò potrebbe accadere semplicemente non notando l'eliminazione o spostando le righe precedenti che accedono alla temperatura dopo la cancellazione.

Ecco un esempio:

int * i = new int(12); 
std::cout << *i << std::endl; // output is 12. 
delete i; 
// i = 0; // This would cause the program to fail on the next line. 
std::cout << *i << std::endl; // output is random for me. 

noti che questo non nasconde un difetto, infatti non impostando il puntatore a null sarebbe, in questo caso, nascondendo il difetto come * i restituisce un valore casuale .

La maggior parte direbbe che i = 0 è probabilmente ottimizzato da un compilatore, in entrambi i casi l'assegnazione a un puntatore è per lo più innocua. Per quanto mi riguarda, prendo sempre la massima cautela nello sviluppo professionale.

Problemi correlati