2010-10-14 5 views
5

Ho un codice che utilizza molti puntatori che puntano allo stesso indirizzo. dato un equivalente semplice esempio:Come eliminare in modo sicuro più puntatori

int *p = new int(1); 
int *q = p; 
int *r = q; 

delete r; r = NULL; // ok 
// delete q; q = NULL; // NOT ok 
// delete p; p = NULL; // NOT ok 

Come eliminare in modo sicuro senza multipla eliminare? Questo è particolarmente difficile se ho molti oggetti che hanno puntatori che puntano tutti allo stesso indirizzo.

+1

Non dovrebbe funzionare? delete null è specificato nello standard, quindi ha permesso e dovrebbe funzionare. OK, non è il miglior stile di codifica ... –

+0

@ Mario: L'eliminazione di NULL è specificata come NO-OP ma il suo richiamo comporta un sovraccarico. –

+6

Il problema q e p non saranno NULL, quindi ci saranno doppie eliminazioni. –

risposta

18

La risposta, senza ricorrere ai puntatori gestiti, è che è necessario sapere se eliminare o meno un puntatore in base a dove è stato assegnato.

Il tuo esempio è un po 'inventato, ma in un'applicazione del mondo reale, l'oggetto responsabile dell'allocazione della memoria sarebbe responsabile della sua distruzione. I metodi e le funzioni che ricevono puntatori già inizializzati e li memorizzano per un certo tempo non cancellano quei puntatori; questa responsabilità è di qualsiasi oggetto originariamente assegnato alla memoria.

Basta ricordare che le chiamate a new devono essere bilanciate dalle chiamate a delete. Ogni volta che si assegna memoria, è noto che si deve scrivere il codice di bilanciamento (spesso un distruttore) per deallocare quella memoria.

+0

IMO questa è la migliore risposta. –

+2

+1: La mia opinione è che shared_ptr dovrebbe essere usato solo nel caso in cui non c'è un chiaro proprietario della memoria e che nella maggior parte dei casi non ne hai bisogno. – n1ckp

+0

Leggere: http://crazyeddiecpp.blogspot.com/2010/12/pet-peeve.html - non si eliminano i puntatori! –

28

Il tuo strumento è shared_ptr della libreria boost. Date un'occhiata alla documentazione: http://www.boost.org/doc/libs/1_44_0/libs/smart_ptr/shared_ptr.htm

Esempio:

void func() { 
    boost::shared_ptr<int> p(new int(10)); 
    boost::shared_ptr<int> q(p); 
    boost::shared_ptr<int> r(q); 

    // will be destructed correctly when they go out of scope. 
} 
+3

+1 - Lo scenario OP di "molti puntatori che puntano allo stesso indirizzo" è ideale per 'shared_ptr' –

+1

I compilatori rilasciati di recente forniscono shared_ptr come membro dello spazio dei nomi tr1. Quindi boost non è richiesto - puoi invece utilizzare tr1 :: shared_ptr. – nobar

+1

I puntatori "grezzi" in C++ sono ereditati da C, ma il problema è che non esprimono affatto la proprietà_ (è uno dei problemi che non possono essere menzionati abbastanza frequentemente, IMHO). Una delle cose più importanti del codice è che deve esprimere le intenzioni dell'autore (inclusa la proprietà), quindi è sempre meglio usare i puntatori intelligenti di boost, o almeno ** std :: auto_ptr **. (Ma i puntatori della vecchia scuola vanno bene per casi semplici, come quelli interni per oggetti "banali"). – riviera

10

La risposta "moderno" è quello di utilizzare un puntatore intelligente e non fare alcun eliminazioni manuali.

boost::shared_ptr<int> p(new int(1)); 
boost::shared_ptr<int> q = p; 
boost::shared_ptr<int> r = q; 

Fine della storia!

+0

+1 per terminare la storia ';)' –

+0

-1 per la pubblicazione di codice non testato. Una buona API di puntatore intelligente (come std :: auto_ptr di boost :: shared_ptr) non consente di assegnare puntatori a puntatori intelligenti o creare puntatori intelligenti impliciti da puntatori utilizzando il costruttore di copie. in questo modo la proprietà non sarebbe evidente. Cambia la tua prima linea per aumentare :: shared_ptr p = boost :: shared_ptr (nuovo int (1)); –

+0

grazie, codice fisso –

0

Ci sono alcuni casi molto rari in cui potresti non essere in grado di utilizzare un puntatore intelligente (probabilmente si tratta di codice vecchio), ma non puoi nemmeno utilizzare un semplice schema di "proprietà".

Immagina di avere uno std::vector<whatever*> e alcuni dei puntatori whatever* puntano allo stesso oggetto. Una pulizia sicura implica che non si cancelli due volte lo stesso, quindi crea un oggetto std::set<whatever*> e elimina solo i puntatori che non sono già presenti nel set. Una volta eliminati tutti gli oggetti appuntiti, entrambi i contenitori possono essere cancellati in modo sicuro.

Il valore di ritorno da insert può essere utilizzato per determinare se l'elemento inserito era nuovo o meno. Non ho ancora testato i seguenti (o utilizzato std :: set per un po '), ma penso che la segue è giusto ...

if (myset.insert (pointervalue).second) 
{ 
    // Value was successfully inserted as a new item 
    delete pointervalue; 
} 

Non si dovrebbe progettare progetti in modo che ciò è necessario, naturalmente, ma non è troppo difficile affrontare la situazione se non puoi evitarlo.

3

Il problema che si sta affrontando è che la semantica della proprietà nel programma non è chiara. Dal punto di vista del design, cerca di determinare chi è il proprietario degli oggetti ad ogni passaggio. In molti casi ciò implicherà che chiunque crei l'oggetto dovrà eliminarlo in seguito, ma in altri casi la proprietà può essere trasferita o persino condivisa.

Una volta che sai chi possiede la memoria, torna al codice e implementalo.Se un oggetto è il solo responsabile per un oggetto diverso, il che dovrebbe tenerlo tramite un puntatore singola proprietà intelligente (std::auto_ptr/unique_ptr) o anche un puntatore grezzo (cercare di evitare questo in quanto è una fonte comune di errori) e il gestore la memoria manualmente. Quindi passare riferimenti o puntatori ad altri oggetti. Quando viene trasferita la proprietà, utilizzare le funzionalità del puntatore intelligente per rendere l'oggetto al nuovo proprietario. Se la proprietà è realmente condivisa (non esiste un chiaro proprietario dell'oggetto assegnato), è possibile utilizzare shared_ptr e lasciare che il puntatore intelligente gestisca la gestione della memoria).

1

Perché stai cercando di eliminare i puntatori arbitrariamente? Ogni oggetto allocato dinamicamente è allocato in una luogo, da un proprietario. E dovrebbe essere responsabilità di un solo proprietario assicurarsi che l'oggetto sia cancellato di nuovo.

In alcuni casi, è possibile trasferire la proprietà a un altro oggetto o componente, nel qual caso la responsabilità dell'eliminazione cambia anche in alcuni casi.

E a volte, si vuole solo dimenticare la proprietà e l'uso condiviso proprietà: tutti coloro che utilizzano le azioni oggetto ownersip, e fintanto che esiste almeno un utente, l'oggetto non deve essere eliminato.

quindi si utilizza shared_ptr.

In breve, utilizzare Raii. Non provare a eliminare manualmente gli oggetti.

0

Non è possibile sapere se la memoria a cui fa riferimento un puntatore è già stato cancellato, come nel tuo caso.
Se non è possibile utilizzare una libreria con puntatori intelligenti, che fanno il conteggio dei riferimenti e non è possibile implementare il proprio schema di conteggio dei riferimenti (se effettivamente è necessario eseguire ciò che si descrive nel post), provare a richiamare realloc sui puntatori.
Ho letto in altri post che, a seconda dell'implementazione, la chiamata a realloc potrebbe non bloccarsi ma restituire un puntatore nullo. In questo caso sai che la memoria referenziata da quel puntatore è stata liberata.
Se funziona come soluzione sporca, non sarà portatile, ma se non hai altra opzione, provalo. Il peggio, naturalmente, sarà quello di mandare in crash la tua applicazione :)

0

Direi che alcune volte, i puntatori intelligenti possono effettivamente rallentare la tua applicazione, anche se non molto. Quello che vorrei fare è creare un metodo, in questo modo:

void safeDelete(void **ptr) 
{ 
    if(*ptr != NULL) 
    { 
    delete ptr; 
    *ptr = NULL; 
    } 
} 

Non sono sicuro se l'ho fatto al 100% in modo corretto, ma quello che fai è che si passa il puntatore in questo metodo che prende il puntatore di un puntatore, verifica che il puntatore a cui punta non sia impostato su NULL, quindi elimina l'oggetto e imposta l'indirizzo su 0 o NULL. Correggimi se questo non è un buon modo per farlo, sono anche nuovo per questo e qualcuno mi ha detto che era un ottimo modo per controllare senza complicarsi.

+0

Questo non risolve il problema; con l'esempio nella domanda, 'safeDelete (& r)' funzionerà, ma non modificherà 'q' o' p', quindi 'safeDelete (& q)' potrebbe andare in crash. – Synxis