2010-02-03 15 views
7

Sto scrivendo una classe template che accetta come input un puntatore e lo memorizza. Il puntatore è destinato a puntare a un oggetto assegnato da un'altra classe e consegnato alla classe contenente questo.Un distruttore - dovrei usare cancellare o cancellare []?

Ora voglio creare un distruttore per questo contenitore. Come posso liberare la memoria puntata da questo puntatore? Non ho modo di sapere a priori se si tratta di un array o di un singolo elemento.

Sono una specie di nuovo in C++, quindi portami con me. Ho sempre usato C e Java è il mio linguaggio OO di scelta, ma tra il voler imparare il C++ e i requisiti di velocità del mio progetto, sono andato con il C++.

Sarebbe una buona idea cambiare il contenitore da un modello a un contenitore per una classe astratta in grado di implementare il proprio distruttore?

+3

La risposta di JonH è giusta, quindi forse dovresti offrire modelli: uno per gli array, uno no. L'altra risposta è di evitare gli array e invece aspettarsi una singola istanza che possa o meno essere una raccolta corretta che ripulisce dopo se stessa, come il vettore <>. –

+1

@Steven Sudit: Penso che dovresti fare una risposta a quel commento. –

+0

Anche questo approccio potrebbe presentare problemi di threading: non è possibile eliminare qualcosa che è stato allocato in un thread diverso. – ChrisF

risposta

6

Se non si sa se è stato assegnato con new o new[], non è sicuro cancellarlo.

Il tuo codice potrebbe sembrare funzionare. Ad esempio, su una piattaforma su cui lavoro, la differenza è importante solo quando si dispone di una matrice di oggetti con distruttori. Quindi, si esegue questa operazione:

// by luck, this works on my preferred platform 
// don't do this - just an example of why your code seems to work 
int *ints = new int[20]; 
delete ints; 

ma poi si esegue questa operazione:

// crashes on my platform 
std::string *strings = new std::string[10]; 
delete strings; 
+1

Non è fortuna, è che 'int' non ha distruttore. –

+5

@Steven: è una fortuna. Corrispondenza errata di 'new' e' delete' porta a comportamento non definito, punto. Non definito anche se un tipo ha un distruttore banale. Per me è del tutto ragionevole sostituire "operatore new" e "operator delete" come se fosse una scelta giusta; e posso aspettarmi che i puntatori nella mia funzione 'delete' siano stati emessi dal corrispondente' new'. – GManNickG

+3

@Steven: è una fortuna perché la piattaforma * sceglie * per ottimizzare le allocazioni di array quando il tipo non ha distruttori. Ma un'altra piattaforma potrebbe gestirlo in modo diverso e quel codice si romperebbe su quest'altra piattaforma. –

5

Risposta breve:

Se si utilizza [] con il nuovo che si desidera utilizzare [] con eliminazione.

//allocate some memory 
myObject* m = new myObject[100]; 

//later on...destructor... 
delete m; //wrong 
delete[] m; //correct 

Quello era nudo le ossa, l'altra cosa che si poteva guardare è boost. Anche abbastanza difficile rispondere considerando che non sei sicuro se sia un array o un singolo oggetto. Puoi controllare questo tramite una bandierina che dice alla tua app se usare cancella o cancella [].

+2

In realtà non risponde alla sua domanda. Presumibilmente capisce che è importante usare la funzione 'cancella' corretta, altrimenti non farebbe la sua domanda. : P – GManNickG

+0

@GMan I verrà modificato per adattarlo alle esigenze :). – JonH

+0

boost sembra risolvere tutto ... a volte hai mai pensato? :) – JonH

0

Un puntatore intelligente come spinta shared_pointer già ha questa coperta, potrebbe usarlo? linky

2

Come regola generale di sviluppo, si dovrebbero usare per un progetto in cui la classe che chiama new dovrebbe anche chiamare delete

+4

Non proprio, questo è esattamente l'opposto della maggior parte delle attuali librerie di puntatori intelligenti: 'std :: auto_ptr ap (new int()); boost :: shared_ptr sp (new int()); 'Entrambi chiamano delete per te. –

6

Devi documentare come questa classe si aspetta di essere utilizzato, e allocare sempre come previsto. Puoi anche passare un flag all'oggetto specificando come dovrebbe distruggere. Dai un'occhiata anche allo smart pointers di boost, che può gestire questa distinzione per te.

+0

Questo è fondamentalmente corretto. Se stai passando un puntatore, ci deve essere A: solo un modo "giusto" per eliminarlo, oppure B: Un'altra variabile "flag" che dice in che modo eliminarlo, oppure C: Un deleter personalizzato, che è comunque molto oltre la comprensione di un principiante. Vai con "B" nel tuo caso IMO. –

+0

@Kevin: solo in pratica? Mi sono perso qualcosa? L'opzione che scelgo non è quella che hai menzionato: porta quel puntatore in un puntatore intelligente appropriato ASAP e lascia che il puntatore intelligente gestisca il modo in cui/se/quando eliminare. Le nozioni di base di * usando * auto_ptr o uno qualsiasi dei puntatori intelligenti di boost non sono molto difficili (la comprensione di tutte le personalizzazioni e soprattutto dei dettagli di implementazione è, comunque). –

+3

Qual è stato il downvote per? –

2

Non dovresti eliminarlo affatto. Se la tua classe prende un puntatore già inizializzato, non è sicuro cancellarlo. Potrebbe non puntare nemmeno a un oggetto sull'heap; chiamare delete o delete[] potrebbe essere disastroso.

L'allocazione e la deallocazione della memoria devono avvenire nello stesso ambito. Quale mai il codice possiede e inizializza l'istanza della tua classe è anche presumibilmente responsabile per l'inizializzazione e il passaggio del puntatore, e che è dove dovrebbe essere il tuo delete.

+3

Quindi non posso usare 'auto_ptr' /' unique_ptr' per passare la proprietà? – GManNickG

+0

@GMan Esistono ovvie eccezioni quando la classe in cui stai passando un puntatore è * progettata * per assumerne la proprietà ... – meagar

1

Poiché il puntatore in C++ non ci dice come è stato assegnato, sì, non c'è modo di decidere quale metodo di deallocazione utilizzare.La soluzione è dare la scelta all'utente che si spera sappia come è stata allocata la memoria. Dai un'occhiata alla libreria Boost smart ptr, in particolare al costruttore shared_ptr con il secondo parametro, per un ottimo esempio.

+0

Dare la scelta all'utente? Non ha molto senso? – JonH

+0

@JonH, per * l'utente del contenitore *, cioè il codice che lo utilizza, non * l'utente alla tastiera * :) –

+0

ahh, ho capito, è stato confuso per un secondo. – JonH

2
  • Utilizzare delete se assegnato con new.
  • Utilizzare delete[] se assegnato con new[].

Dopo queste affermazioni, se avete ancora un problema (forse si vuole eliminare un oggetto che è stato creato da qualcun altro), allora si sta rompendo la terza regola:

  • cancellare sempre quello che si creato. Corollario, non eliminare mai ciò che non hai creato.
+0

Questa terza regola deve essere interrotta in alcuni casi, anche se sono d'accordo con essa in generale. –

+0

Il corollario della terza regola è "non creare mai puntatori intelligenti" :-) –

+0

In realtà no. Se si crea un oggetto, lo si memorizza in un puntatore intelligente e si assicura che solo il puntatore intelligente venga pubblicato nel mondo esterno, quindi si è assunto la responsabilità di creare e eliminare l'oggetto nello stesso posto, il che è positivo, anche se l'effettivo la cancellazione non è fatta da te. –

2

(Moving mio commento in una risposta, a richiesta.)

risposta di Jonh ha ragione (sull'uso di distruzione di matrice solo quando si è utilizzato la costruzione array), quindi forse si dovrebbe offrire modelli: uno per array, uno no.

L'altra risposta è di evitare gli array e invece si aspettano una singola istanza che può o meno essere una raccolta corretta che ripulisce dopo se stessa, come ad esempio il vettore <>.

modificare

Rubare palesemente da Roger Pate, aggiungerò che si potrebbe richiedere l'uso di un puntatore intelligente, il che equivale a una raccolta singolo elemento.

0

In parole povere, dato solo un puntatore alla memoria allocata dinamicamente non c'è modo di determinare come de-allocarlo in modo sicuro. Il puntatore avrebbe potuto essere allocata in una delle seguenti modalità:

  • usando nuovo
  • utilizzando nuovo []
  • usando malloc
  • utilizzando una funzione definita dall'utente
  • ecc

In tutti i casi prima di poter deallocare la memoria è necessario sapere come è stata allocata.

+0

Fondamentalmente deciderai se la tua classe rappresenta uno smart_ptr o uno smart_array e lasci al chiamante il compito di fornire un puntatore opportunamente allocato alla tua classe. – UncleBens

+0

@UncleBens Non sono sicuro che il tuo commento significhi WRT la mia risposta - nonostante il suo nome un "puntatore intelligente" non sia un puntatore. –

+0

Voglio dire che è giusto che se tutto quello che hai è un puntatore, allora non puoi dire come è stato assegnato. Non penso che finisca qui. Semplicemente dici, ho una classe qui che chiamerà 'delete' /' delete [] '/' free() '/ funzione di deallocazione definita dall'utente, e spetta all'utente della tua classe provvedere adeguatamente puntatore assegnato. Fondamentalmente fai qualcosa che ha senso, e il resto non è un tuo problema. – UncleBens

2

Se si dispone di una classe che accetta un puntatore, ne assume la proprietà, quindi il contratto per l'utilizzo della classe deve includere uno di un paio di elementi. O:

  • l'interfaccia deve indicare in che modo è stato allocato l'oggetto puntato dal puntatore in modo che il nuovo proprietario possa sapere come rilasciare l'oggetto in modo sicuro. Questa opzione ha il vantaggio di mantenere le cose semplici (a un livello comunque), ma non è flessibile - la classe non può gestire l'assunzione di proprietà di oggetti statici e oggetti allocati dinamicamente.

o

  • l'interfaccia deve includere un meccanismo in cui una politica deallocazione può essere specificato da tutto ciò che è dando il puntatore alla classe. Questo può essere semplice quanto fornire un meccanismo per passare in un funtore (o anche un semplice puntatore a funzione vecchia) che verrà chiamato per deallocare l'oggetto (preferibilmente nella stessa funzione/costruttore che passa nel puntatore stesso). Questo rende la classe discutibilmente più complicata da usare (ma avere una politica di default per chiamare delete sul puntatore, ad esempio, potrebbe renderlo facile da usare come l'opzione 1 per la maggior parte degli usi). Ora se qualcuno vuole dare alla classe un puntatore a un oggetto assegnato staticamente, può passare in un functor no-op quindi nulla accade quando la classe vuole deallocarlo o un functor a un'operazione delete[] se l'oggetto è stato assegnato da new[] , ecc.
+0

+1 Questa è la risposta corretta. Sono sorpreso che non sia revocato. –