È buona norma NULL un puntatore dopo averlo eliminato?


150

Inizierò dicendo: usa i puntatori intelligenti e non dovrai mai preoccuparti di questo.

Quali sono i problemi con il seguente codice?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

Ciò è stato innescato da una risposta e commenti a un'altra domanda. Un commento di Neil Butterworth ha generato alcuni voti:

L'impostazione dei puntatori su NULL dopo l'eliminazione non è una buona pratica universale in C ++. Ci sono momenti in cui è una buona cosa da fare e momenti in cui è inutile e può nascondere errori.

Ci sono molte circostanze in cui non sarebbe d'aiuto. Ma nella mia esperienza, non può far male. Qualcuno mi illumina.


6
@Andre: tecnicamente non è definito. Ciò che è probabile che accada è che accedi alla stessa memoria di prima, ma ora può essere utilizzata da qualcos'altro. Se si cancella la memoria due volte, è probabile che si rovini l'esecuzione del programma in un modo difficile da trovare. È sicuro per deleteun puntatore nullo, tuttavia, che è una ragione per cui azzerare un puntatore può essere buono.
David Thornley,

5
@ André Pena, non è definito. Spesso non è nemmeno ripetibile. Impostare il puntatore su NULL per rendere più visibile l'errore durante il debug e forse per renderlo più ripetibile.
Mark Ransom,

3
@ André: nessuno lo sa. È un comportamento indefinito. Potrebbe arrestarsi in modo anomalo con una violazione di accesso o sovrascrivere la memoria utilizzata dal resto dell'applicazione. Lo standard linguistico non fornisce alcuna garanzia su ciò che accade e pertanto non è possibile fidarsi della propria applicazione una volta avvenuta. Si potrebbe aver sparato i missili nucleari o formattato il vostro hard disk. può corrompere la memoria della tua app o far volare i demoni dal tuo naso. Tutte le scommesse sono chiuse.
jalf

16
I demoni volanti sono una caratteristica, non un bug.
jball,

3
Questa domanda non è un duplicato perché l'altra domanda riguarda C e questa riguarda C ++. Molte risposte dipendono da cose come i puntatori intelligenti, che non sono disponibili in C ++.
Adrian McCarthy,

Risposte:


86

Impostando un puntatore su 0 (che è "null" in C ++ standard, il valore NULL definito da C è leggermente diverso) si evitano arresti anomali su doppie eliminazioni.

Considera quanto segue:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

Mentre:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

In altre parole, se non si impostano i puntatori eliminati su 0, si avranno problemi se si eseguono doppie eliminazioni. Un argomento contro l'impostazione dei puntatori su 0 dopo l'eliminazione sarebbe che così facendo maschera solo i double bugs e li lascia non gestiti.

È meglio non avere doppi bug di eliminazione, ovviamente, ma a seconda della semantica della proprietà e dei cicli di vita degli oggetti, questo può essere difficile da ottenere in pratica. Preferisco un bug di doppia eliminazione mascherato rispetto a UB.

Infine, un sidoto sulla gestione dell'allocazione degli oggetti, ti suggerisco di dare un'occhiata alla std::unique_ptrproprietà rigorosa / singolare, std::shared_ptralla proprietà condivisa o ad un'altra implementazione del puntatore intelligente, a seconda delle tue esigenze.


13
L'applicazione non si bloccherà sempre in caso di doppia eliminazione. A seconda di cosa succede tra le due eliminazioni, tutto può succedere. Molto probabilmente, corromperai il tuo heap e, in seguito, ti schianterai in un pezzo di codice completamente non correlato. Mentre un segfault di solito è meglio che ignorare silenziosamente l'errore, in questo caso il segfault non è garantito ed è di utilità discutibile.
Adam Rosenfield,

28
Il problema qui è il fatto che hai una doppia eliminazione. Rendere il puntatore NULL nasconde solo il fatto che non lo corregge o lo rende più sicuro. Imagaine un mainainer che ritorna un anno dopo e vede il foo cancellato. Ora crede di poter riutilizzare il puntatore, sfortunatamente potrebbe mancare la seconda eliminazione (potrebbe non essere nemmeno nella stessa funzione) e ora il riutilizzo del puntatore ora viene cestinato dalla seconda eliminazione. Qualsiasi accesso dopo la seconda eliminazione è ora un grave problema.
Martin York,

11
È vero che l'impostazione del puntatore NULLpuò mascherare un doppio errore di eliminazione. (Alcuni potrebbero considerare questa maschera come una soluzione - lo è, ma non molto buona poiché non arriva alla radice del problema.) Ma non impostarla su NULL maschera il lontano (LONTANO!) Di più problemi comuni di accesso ai dati dopo che sono stati eliminati.
Adrian McCarthy,

AFAIK, std :: auto_ptr è stato deprecato nel prossimo standard c ++
rafak,

Non direi deprecato, sembra che l'idea sia sparita. Piuttosto, viene sostituito con unique_ptr, che fa ciò che ha auto_ptrcercato di fare, con la semantica di spostamento.
GManNickG

55

Impostare i puntatori su NULL dopo aver eliminato ciò che indicava di certo non può far male, ma spesso è un po 'un cerotto su un problema più fondamentale: perché stai usando un puntatore in primo luogo? Posso vedere due motivi tipici:

  • Volevi semplicemente qualcosa assegnato all'heap. Nel qual caso avvolgerlo in un oggetto RAII sarebbe stato molto più sicuro e pulito. Terminare l'ambito dell'oggetto RAII quando non è più necessario l'oggetto. Ecco come std::vectorfunziona e risolve il problema di lasciare accidentalmente i puntatori nella memoria allocata. Non ci sono puntatori.
  • O forse volevi qualche semantica di proprietà condivisa complessa. Il puntatore restituito newpotrebbe non essere lo stesso di quello su cui deleteè stato chiamato. Nel frattempo più oggetti potrebbero aver usato l'oggetto contemporaneamente. In tal caso, sarebbe stato preferibile un puntatore condiviso o qualcosa di simile.

La mia regola empirica è che se si lasciano dei puntatori nel codice utente, lo si sta facendo in modo sbagliato. Il puntatore non dovrebbe essere lì per indicare la spazzatura in primo luogo. Perché un oggetto non si assume la responsabilità di garantirne la validità? Perché il suo ambito non termina quando l'oggetto puntato fa?


15
Quindi stai argomentando che in primo luogo non avrebbe dovuto esserci un puntatore non elaborato, e tutto ciò che comporta tale puntatore non dovrebbe essere benedetto con il termine "buone pratiche"? Giusto.
Mark Ransom,

7
Bene, più o meno. Non direi che nulla che implichi un puntatore grezzo può essere definito una buona pratica. Solo che è l'eccezione piuttosto che la regola. Di solito, la presenza del puntatore indica che c'è qualcosa di sbagliato a un livello più profondo.
jalf

3
ma per rispondere alla domanda immediata, no, non vedo come impostare i puntatori su null possa mai causare errori.
JALF

7
Non sono d'accordo - ci sono casi in cui un puntatore è buono da usare. Ad esempio, ci sono 2 variabili nello stack e si desidera sceglierne una. Oppure vuoi passare una variabile opzionale a una funzione. Direi che non dovresti mai usare un puntatore non elaborato insieme a new.
rlbond,

4
quando un puntatore è uscito dal campo di applicazione, non vedo come qualcosa o chiunque potrebbe aver bisogno di gestirlo.
jalf

43

Ho una migliore pratica ancora migliore: ove possibile, termina l'ambito della variabile!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}

17
Sì, RAII è tuo amico. Avvolgilo in una classe e diventa ancora più semplice. O non gestire affatto la memoria usando STL!
Brian,

24
Sì, questa è l'opzione migliore. Non risponde alla domanda però.
Mark Ransom,

3
Questo sembra essere solo un sottoprodotto dell'utilizzo del periodo degli ambiti di funzione e non risolve davvero il problema. Quando si utilizzano i puntatori, di solito si passano copie di essi a diversi livelli di profondità e quindi il metodo è davvero insignificante nel tentativo di affrontare il problema. Anche se concordo sul fatto che un buon design ti aiuterà a isolare gli errori, non credo che il tuo metodo sia il mezzo principale a tal fine.
San Jacinto,

2
Vieni a pensarci, se potessi farlo, perché non dovresti semplicemente dimenticare l'heap e togliere tutta la tua memoria dallo stack?
San Jacinto,

4
Il mio esempio è intenzionalmente minimo. Ad esempio, anziché nuovo, forse l'oggetto viene creato da una factory, nel qual caso non può andare nello stack. O forse non è stato creato all'inizio dell'ambito, ma si trova in una struttura. Quello che sto illustrando è che questo approccio troverà qualsiasi uso improprio del puntatore in fase di compilazione , mentre NULLing troverà qualsiasi uso improprio in fase di esecuzione .
Don Neufeld,

31

Ho sempre impostato un puntatore su NULL(ora nullptr) dopo aver eliminato gli oggetti a cui punta.

  1. Può aiutare a catturare molti riferimenti alla memoria liberata (supponendo che i guasti della tua piattaforma su un deref di un puntatore nullo).

  2. Non catturerà tutti i riferimenti alla memoria libera se, ad esempio, ci sono copie del puntatore in giro. Ma alcuni è meglio di nessuno.

  3. Maschera una doppia eliminazione, ma trovo che siano molto meno comuni degli accessi alla memoria già liberata.

  4. In molti casi il compilatore lo ottimizzerà via. Quindi l'argomento che non è necessario non mi convince.

  5. Se stai già usando RAII, allora non ci sono molti deletes nel tuo codice per cominciare, quindi l'argomento che l'assegnazione extra causa disordine non mi convince.

  6. Spesso è conveniente, durante il debug, vedere il valore null piuttosto che un puntatore stantio.

  7. Se questo ti dà ancora fastidio, usa invece un puntatore intelligente o un riferimento.

Ho anche impostato altri tipi di handle di risorse sul valore senza risorse quando la risorsa è libera (che di solito è solo nel distruttore di un wrapper RAII scritto per incapsulare la risorsa).

Ho lavorato su un grande prodotto commerciale (9 milioni di dichiarazioni) (principalmente in C). A un certo punto, abbiamo usato la magia macro per annullare il puntatore quando la memoria è stata liberata. Ciò ha immediatamente rivelato molti bug in agguato che sono stati prontamente risolti. Per quanto posso ricordare, non abbiamo mai avuto un bug double-free.

Aggiornamento: Microsoft ritiene che sia una buona pratica per la sicurezza e raccomanda tale pratica nei propri criteri SDL. Apparentemente MSVC ++ 11 calpesterà automaticamente il puntatore eliminato (in molte circostanze) se si compila con l'opzione / SDL.


12

In primo luogo, ci sono molte domande esistenti su questo e argomenti strettamente correlati, ad esempio Perché l'eliminazione non imposta il puntatore su NULL? .

Nel tuo codice, il problema è quello che succede (usa p). Ad esempio, se da qualche parte hai un codice come questo:

Foo * p2 = p;

quindi impostare p su NULL compie pochissimo, dato che hai ancora il puntatore p2 di cui preoccuparti.

Questo non significa che l'impostazione di un puntatore su NULL sia sempre inutile. Ad esempio, se p fosse una variabile membro che punta a una risorsa la cui durata non era esattamente la stessa della classe che contiene p, l'impostazione di p su NULL potrebbe essere un modo utile per indicare la presenza o l'assenza della risorsa.


1
Sono d'accordo che ci sono momenti in cui non sarà di aiuto, ma sembra che tu insinui che potrebbe essere attivamente dannoso. Era questo il tuo intento o l'ho letto male?
Mark Ransom,

1
Se esiste una copia del puntatore è irrilevante alla domanda se la variabile puntatore debba essere impostata su NULL. Impostarlo su NULL è una buona pratica per gli stessi motivi di pulizia dei piatti dopo aver finito con la cena è una buona pratica - sebbene non sia una protezione contro tutti i bug che un codice può avere, promuove una buona salute del codice.
Franci Penov,

2
@Franci Molte persone sembrano non essere d'accordo con te. E se esiste una copia sicuramente è rilevante se si tenta di utilizzare la copia dopo aver eliminato l'originale.

3
Franci, c'è una differenza. Pulisci i piatti perché li usi di nuovo. Non è necessario il puntatore dopo averlo eliminato. Dovrebbe essere l'ultima cosa che fai. La migliore pratica è di evitare del tutto la situazione.
GManNickG,

1
Puoi riutilizzare una variabile, ma non è più un caso di programmazione difensiva; è come hai progettato la soluzione al problema in questione. L'OP sta discutendo se questo stile difensivo è qualcosa su cui dovremmo lottare, non di cui avremo mai impostato un puntatore su null. E idealmente, alla tua domanda, sì! Non usare i puntatori dopo averli eliminati!
GManNickG

7

Se c'è più codice dopo il delete , Sì. Quando il puntatore viene eliminato in un costruttore o alla fine del metodo o della funzione, No.

Il punto di questa parabola è ricordare al programmatore, durante il runtime, che l'oggetto è già stato cancellato.

Una pratica ancora migliore consiste nell'utilizzare i puntatori intelligenti (condivisi o con ambito) che eliminano automagicamente i loro oggetti di destinazione.


Tutti (incluso l'interrogatore originale) concordano sul fatto che i puntatori intelligenti siano la strada da percorrere. Il codice si evolve. Dopo l'eliminazione potrebbe non esserci più codice dopo l'eliminazione, ma è probabile che cambi nel tempo. Inserire l'incarico aiuta quando ciò accade (e non costa quasi nulla nel frattempo).
Adrian McCarthy,

3

Come altri hanno già detto, delete ptr; ptr = 0;non farà in modo che i demoni volino fuori dal tuo naso. Tuttavia, incoraggia l'uso di ptrcome una sorta di bandiera. Il codice diventa disseminato deletee impostando il puntatore su NULL. Il prossimo passo è spargere il if (arg == NULL) return;tuo codice per proteggerti dall'uso accidentale di un NULLpuntatore. Il problema si verifica quando i controlli NULLdiventano il mezzo principale per verificare lo stato di un oggetto o programma.

Sono sicuro che c'è un odore di codice sull'uso di un puntatore come bandiera da qualche parte, ma non ne ho trovato uno.


9
Non c'è niente di sbagliato nell'usare un puntatore come bandiera. Se stai usando un puntatore e NULLnon è un valore valido, probabilmente dovresti usare un riferimento.
Adrian McCarthy,

2

Cambierò leggermente la tua domanda:

Utilizzeresti un puntatore non inizializzato? Sai, uno che non hai impostato su NULL o allocare la memoria a cui punta?

Esistono due scenari in cui è possibile saltare l'impostazione del puntatore su NULL:

  • la variabile puntatore esce immediatamente dall'ambito
  • hai sovraccaricato la semantica del puntatore e stai usando il suo valore non solo come puntatore di memoria, ma anche come chiave o valore grezzo. questo approccio tuttavia soffre di altri problemi.

Nel frattempo, sostenere che l'impostazione del puntatore su NULL potrebbe nascondermi degli errori sembra come sostenere che non si dovrebbe correggere un bug perché la correzione potrebbe nascondere un altro bug. Gli unici bug che potrebbero mostrare se il puntatore non è impostato su NULL sarebbero quelli che tentano di usare il puntatore. Ma impostarlo su NULL causerebbe esattamente lo stesso bug che mostrerebbe se lo si utilizza con memoria libera, no?


(A) "sembra sostenere che non si debba correggere un bug" Non impostare un puntatore su NULL non è un bug. (B) "Ma impostarlo su NULL in realtà causerebbe esattamente lo stesso bug" No. L'impostazione su NULL nasconde la doppia eliminazione . (C) Riepilogo: l'impostazione su NULL nasconde la doppia eliminazione, ma espone riferimenti non aggiornati. Non impostare su NULL può nascondere riferimenti non aggiornati, ma espone le doppie eliminazioni. Entrambe le parti concordano sul fatto che il vero problema è quello di correggere i riferimenti non aggiornati e le doppie eliminazioni.
Mooing Duck,

2

Se non hai altri vincoli che ti obbligano a impostare o non impostare il puntatore su NULL dopo averlo eliminato (uno di questi vincoli è stato citato da Neil Butterworth ), la mia preferenza personale è di lasciarlo.

Per me la domanda non è "è una buona idea?" ma "quale comportamento dovrei prevenire o consentire di fare questo?" Ad esempio, se ciò consente ad altro codice di vedere che il puntatore non è più disponibile, perché un altro codice sta anche tentando di guardare i puntatori liberati dopo che sono stati liberati? Di solito, è un bug.

Fa anche più lavoro del necessario e ostacola il debug post mortem. Meno tocchi la memoria dopo che non ti serve, più è facile capire perché qualcosa si è bloccato. Molte volte ho fatto affidamento sul fatto che la memoria è in uno stato simile a quando si è verificato un determinato bug per diagnosticare e correggere detto bug.


2

L'annullamento esplicito dopo l'eliminazione suggerisce fortemente a un lettore che il puntatore rappresenta qualcosa che è concettualmente facoltativo . Se lo vedessi, comincerei a preoccuparmi che ovunque nella fonte venga usato il puntatore, che dovrebbe essere prima testato contro NULL.

Se questo è ciò che realmente intendi, è meglio renderlo esplicito nella fonte usando qualcosa come boost :: opzionale

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

Ma se davvero volevi che la gente sapesse che il puntatore è "andato male", presenterò un accordo al 100% con coloro che affermano che la cosa migliore da fare è renderlo fuori portata. Quindi stai usando il compilatore per prevenire la possibilità di cattive dereferenze in fase di esecuzione.

Questo è il bambino in tutta l'acqua del bagno C ++, non dovrebbe buttarlo via. :)


2

In un programma ben strutturato con controllo degli errori appropriato, non vi è alcun motivo per non assegnarlo null. 0si distingue come un valore non valido universalmente riconosciuto in questo contesto. Fallire duramente e fallire presto.

Molti degli argomenti contro l'assegnazione 0suggeriscono che potrebbe nascondere un bug o complicare il flusso di controllo. Fondamentalmente, questo è o un errore a monte (non colpa tua (scusate il cattivo gioco di parole)) o un altro errore per conto del programmatore - forse anche un'indicazione che il flusso del programma è diventato troppo complesso.

Se il programmatore vuole introdurre l'uso di un puntatore che può essere nullo come valore speciale e scrivere tutto il necessario schivando attorno a ciò, questa è una complicazione che hanno deliberatamente introdotto. Migliore è la quarantena, prima si trovano casi di uso improprio e meno sono in grado di diffondersi in altri programmi.

Programmi ben strutturati possono essere progettati usando le funzionalità C ++ per evitare questi casi. Puoi usare i riferimenti, oppure puoi semplicemente dire "passare / utilizzare argomenti nulli o non validi è un errore" - un approccio che è ugualmente applicabile ai contenitori, come i puntatori intelligenti. L'aumento del comportamento coerente e corretto impedisce a questi bug di andare lontano.

Da lì, hai solo un ambito e un contesto molto limitati in cui può esistere (o è consentito) un puntatore nullo.

Lo stesso può essere applicato ai puntatori che non lo sono const. Seguire il valore di un puntatore è banale perché il suo ambito è così piccolo e l'uso improprio è controllato e ben definito. Se il tuo set di strumenti e i tuoi ingegneri non riescono a seguire il programma dopo una lettura veloce o c'è un controllo degli errori inappropriato o un flusso di programma incoerente / indulgente, hai altri, maggiori problemi.

Infine, il tuo compilatore e l'ambiente probabilmente hanno delle protezioni per i momenti in cui desideri introdurre errori (scarabocchiare), rilevare accessi alla memoria liberata e catturare altri UB correlati. Puoi anche introdurre una diagnostica simile nei tuoi programmi, spesso senza influire sui programmi esistenti.


1

Lasciami espandere ciò che hai già inserito nella tua domanda.

Ecco cosa hai inserito nella tua domanda, in forma di punto elenco:


L'impostazione dei puntatori su NULL dopo l'eliminazione non è una buona pratica universale in C ++. Ci sono momenti in cui:

  • è una buona cosa da fare
  • e volte in cui è inutile e può nascondere errori.

Tuttavia, non ci sono momenti in cui questo è male ! Si Non introdurre più bug da annullamento dei esplicitamente, non sarà perdite di memoria, non sarà causa comportamento non definito per accadere.

Quindi, in caso di dubbio, basta annullarlo.

Detto questo, se ritieni di dover annullare esplicitamente qualche puntatore, allora per me sembra che tu non abbia diviso abbastanza un metodo, e dovresti guardare l'approccio di refactoring chiamato "Metodo di estrazione" per suddividere il metodo in parti separate.


Non sono d'accordo con "non ci sono momenti in cui questo è male". Considera la quantità di cutter introdotta da questo linguaggio. Hai un'intestazione inclusa in ogni unità che elimina qualcosa e tutte quelle posizioni di eliminazione diventano leggermente meno dirette.
GManNickG,

Ci sono momenti in cui è male. Se qualcuno prova a dereferenziare il puntatore cancellato-ora-nullo quando non dovrebbe, probabilmente non si arresta in modo anomalo e quel bug è "nascosto". Se dereferenziano il puntatore eliminato che contiene ancora un valore casuale, è probabile che tu lo noti e il bug sarà più facile da vedere.
Carson Myers,

@Carson: La mia esperienza è piuttosto l'opposto: la mancata presentazione di un valore nullptr quasi sicuramente bloccherà l'applicazione e può essere catturata da un debugger. Dereferenziare un puntatore penzolante di solito non produce immediatamente un problema, ma spesso porta solo a risultati errati o altri errori lungo la linea.
MikeMB,

@MikeMB Sono completamente d'accordo, le mie opinioni su questo sono cambiate sostanzialmente negli ultimi ~ 6,5 anni
Carson Myers,

In termini di programmatore, eravamo tutti qualcun altro 6-7 anni fa :) Non sono nemmeno sicuro che avrei osato rispondere a una domanda C / C ++ oggi :)
Lasse V. Karlsen,

1

Sì.

L'unico "danno" che può fare è introdurre inefficienza (un'operazione di archiviazione non necessaria) nel programma - ma questo sovraccarico sarà insignificante rispetto al costo di allocazione e liberazione del blocco di memoria nella maggior parte dei casi.

Se non lo fai, ti sarà avere alcune brutte puntatore bug derefernce un giorno.

Uso sempre una macro per eliminare:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(e simili per un array, free (), rilasci di handle)

È inoltre possibile scrivere metodi di "eliminazione automatica" che prendono un riferimento al puntatore del codice chiamante, in modo da forzare il puntatore del codice chiamante su NULL. Ad esempio, per eliminare una sottostruttura di molti oggetti:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

modificare

Sì, queste tecniche violano alcune regole sull'uso delle macro (e sì, in questi giorni probabilmente potresti ottenere lo stesso risultato con i modelli) - ma usando molti anni non ho mai avuto accesso alla memoria morta - uno dei più cattivi e difficili e la maggior parte del tempo necessario per il debug dei problemi che si possono affrontare. In pratica per molti anni hanno eliminato efficacemente una intera classe di bug da ogni squadra su cui li ho introdotti.

Esistono anche molti modi per implementare quanto sopra: sto solo cercando di illustrare l'idea di forzare le persone a NULL un puntatore se eliminano un oggetto, piuttosto che fornire loro un mezzo per liberare la memoria che non NULL il puntatore del chiamante .

Naturalmente, l'esempio sopra è solo un passo verso un puntatore automatico. Il che non ho suggerito perché l'OP stava specificatamente chiedendo il caso di non usare un puntatore automatico.


2
Le macro sono una cattiva idea, in particolare quando sembrano normali funzioni. Se vuoi farlo, usa una funzione basata su modelli.

2
Wow ... Non ho mai visto nulla di simile anObject->Delete(anObject)invalidare il anObjectpuntatore. È semplicemente spaventoso. Dovresti creare un metodo statico per questo in modo da essere costretto a fare TreeItem::Delete(anObject)almeno.
D.Shawley,

Spiacenti, è stato digitato come una funzione anziché utilizzare il modulo "questa è una macro" maiuscola. Corretto. Ha anche aggiunto un commento per spiegarmi meglio.
Jason Williams,

E hai ragione, il mio esempio scosso rapidamente è stato spazzatura! Risolto :-). Stavo solo cercando di pensare a un rapido esempio per illustrare questa idea: qualsiasi codice che elimina un puntatore dovrebbe garantire che il puntatore sia impostato su NULL, anche se qualcun altro (il chiamante) possiede quel puntatore. Quindi passa sempre un riferimento al puntatore in modo che possa essere forzato a NULL nel punto di eliminazione.
Jason Williams,

1

"Ci sono volte in cui è una buona cosa da fare e volte in cui è inutile e può nascondere errori"

Riesco a vedere due problemi: quel semplice codice:

delete myObj;
myobj = 0

diventa un for-liner in ambiente multithread:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

La "migliore pratica" di Don Neufeld non si applica sempre. Ad esempio, in un progetto automobilistico abbiamo dovuto impostare i puntatori su 0 anche nei distruttori. Posso immaginare che in software critici per la sicurezza tali regole non siano rare. È più facile (e saggio) seguirli che cercare di persuadere il team / checker del codice per ogni utilizzo del puntatore nel codice, che una riga che annulla questo puntatore è ridondante.

Un altro pericolo è fare affidamento su questa tecnica nel codice che utilizza eccezioni:

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

In tale codice o si produce una perdita di risorse e si rimanda il problema o il processo si arresta in modo anomalo.

Quindi, questi due problemi che mi passano spontaneamente in testa (Herb Sutter direbbe sicuramente di più) mi fanno tutte le domande del tipo "Come evitare di usare i puntatori intelligenti e fare il lavoro in sicurezza con i normali puntatori" come obsoleti.


Non riesco a vedere come il 4-liner sia significativamente più complesso di un 3-liner (uno dovrebbe usare comunque lock_guards) e se il tuo distruttore lancia sei comunque nei guai.
MikeMB,

Quando ho visto questa risposta per la prima volta, non ho capito perché avresti voluto annullare un puntatore in un distruttore, ma ora lo faccio - è per il caso in cui l'oggetto proprietario del puntatore viene utilizzato dopo che è stato eliminato!
Mark Ransom,


0

Se devi riallocare il puntatore prima di riutilizzarlo (dereferenziandolo, passandolo a una funzione, ecc.), Rendere il puntatore NULL è solo un'operazione aggiuntiva. Tuttavia, se non si è sicuri che verrà riallocato o meno prima che venga riutilizzato, impostarlo su NULL è una buona idea.

Come molti hanno già detto, è ovviamente molto più semplice utilizzare solo i puntatori intelligenti.

Modifica: come ha detto Thomas Matthews in questa precedente risposta , se un puntatore viene eliminato in un distruttore, non è necessario assegnargli NULL poiché non verrà riutilizzato perché l'oggetto è già stato distrutto.


0

Posso immaginare di impostare un puntatore su NULL dopo averlo eliminato, utile in rari casi in cui esiste uno scenario legittimo di riutilizzo in una singola funzione (o oggetto). Altrimenti non ha senso - un puntatore deve indicare qualcosa di significativo finché esiste - punto.


0

Se il codice non appartiene alla parte più critica delle prestazioni della tua applicazione, mantienila semplice e usa shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

Esegue il conteggio dei riferimenti ed è thread-safe. Puoi trovarlo nel tr1 (std :: tr1 namespace, #include <memory>) o se il tuo compilatore non lo fornisce, prendilo da boost.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.