2012-01-18 17 views
19

Ho letto qualcosa di simile quando si utilizza il posizionamento nuovo quindi è necessario chiamare manualmente il distruttore.Come liberare correttamente la memoria allocata dal posizionamento nuovo?

Si consideri il codice folowing:

// Allocate memory ourself 
char* pMemory = new char[ sizeof(MyClass)]; 

// Construct the object ourself 
MyClass* pMyClass = new(pMemory) MyClass(); 

// The destruction of object is our duty. 
pMyClass->~MyClass(); 

Per quanto ne so operatore delete normalmente chiama il distruttore e poi dealloca la memoria, giusto? Allora perché non usiamo invece delete?

delete pMyClass; //what's wrong with that? 

nel primo caso siamo costretti a impostare pMyClass a nullptr dopo che noi chiamiamo distruttore come questo:

pMyClass->~MyClass(); 
pMyClass = nullptr; // is that correct? 

MA il distruttore non ha rilasciare la memoria, giusto? Quindi sarebbe una perdita di memoria?

Sono confuso, puoi spiegarlo?

+1

+1 Bella domanda, mi piacerebbe anche capirlo. – AraK

+12

Bene, tecnicamente, il posizionamento 'new' non alloca memoria. Ma è un dettaglio in questo contesto. – Griwes

+1

@Griwes: +1 Probabilmente è un punto fondamentale :) –

risposta

32

L'utilizzo dell'operatore new fa due cose, chiama la funzione operator new che alloca la memoria e quindi utilizza la posizione nuova, per creare l'oggetto in tale memoria. L'operatore delete chiama il distruttore dell'oggetto e quindi chiama operator delete. Sì, i nomi sono confusi.

Dal momento che nel tuo caso hai utilizzato il posizionamento nuovo manualmente, devi anche chiamare manualmente il distruttore. Dal momento che hai assegnato la memoria manualmente, devi rilasciarlo manualmente. Ricordare che se si assegna con new, deve essere presente un numero delete corrispondente. Sempre.

Tuttavia, nuova collocazione è progettato per funzionare con i buffer interni, nonché (e altri scenari), in cui i tamponi erano non assegnato con operator new, motivo per cui non si dovrebbe chiamare operator delete su di loro.

#include <type_traits> 

struct buffer_struct { 
    std::aligned_storage<sizeof(MyClass)>::type buffer; 
}; 
int main() { 
    buffer_struct a; 
    MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a 
    //stuff 
    pMyClass->~MyClass(); //can't use delete, because there's no `new`. 
    return 0; 
} 

Lo scopo della classe buffer_struct è quello di creare e distruggere il deposito in qualsiasi modo, mentre main si occupa della costruzione/distruzione di MyClass, notare come i due sono (quasi *) completamente separate le une dalle altre .

* dobbiamo essere sicuri di stoccaggio deve essere abbastanza grande

+2

Questa è la radice della domanda: "Tuttavia, il posizionamento nuovo è progettato per i buffer interni, che a loro volta non erano allocati con nuovi, motivo per cui non dovresti chiamare eliminare su di essi." Non si libera la memoria perché si utilizzerà quel pezzo del buffer per un altro oggetto dello stesso tipo in un momento futuro. Chiami il distruttore in modo che l'istanza possa ripulire. –

+0

Bel esempio, Quindi per cancellare il buffer che chiamiamo: cancella [] a.buffer se sarebbe dinamicamente, e come chiamiamo distruttore di MyClass allora? – codekiddy

+0

@codekiddy: Dato che 'a.buffer' non è allocato dinamicamente, non ha bisogno di essere deallocato. Nel mio esempio viene automaticamente assegnato, quindi il buffer è completamente automatico in ogni modo. L'unica preoccupazione è il posizionamento nuovo della classe e quindi chiamare il distruttore di quella classe, come mostra il mio codice di esempio. –

9

Una ragione questo è sbagliato:

delete pMyClass; 

è che è necessario eliminare pMemory con delete[] dal momento che è un array:

delete[] pMemory; 

Non si può fare entrambe le cose di cui sopra.

Analogamente, è possibile chiedere perché non è possibile utilizzare malloc() per allocare memoria, posizionamento nuovo per costruire un oggetto, quindi delete per eliminare e liberare memoria. Il motivo è che devi corrispondere a malloc() e free(), non a malloc() e delete.

Nel mondo reale, le chiamate di distruttore nuove ed esplicite di posizionamento non vengono quasi mai utilizzate. Potrebbero essere utilizzati internamente dall'implementazione della libreria standard (o per altre programmazioni a livello di sistema come indicato nei commenti), ma i normali programmatori non li usano. Non ho mai usato questi trucchi per il codice di produzione in molti anni di lavoro in C++.

+0

Solo per dirlo: il posizionamento 'new' è anche usato frequentemente quando si fa lo sviluppo del sistema operativo o qualcosa di simile in C++. – Griwes

+0

Salve, quindi non dobbiamo chiamare manualmente il distruttore poiché delete [] pMemory chiamerà il distruttore E libererà la memoria sin'it? – codekiddy

+2

@codekiddy: 'delete [] pMemory' fa * not * chiama il distruttore per il tuo oggetto. –

4

È necessario distinguere tra l'operatore ed deleteoperator delete. In particolare, se si sta utilizzando nuova collocazione, si richiama esplicitamente il distruttore e quindi chiamare operator delete (e non l'operatore delete) per rilasciare la memoria, vale a dire

X *x = static_cast<X*>(::operator new(sizeof(X))); 
new(x) X; 
x->~X(); 
::operator delete(x); 

Si noti che questo utilizza operator delete, che è in basso a livello rispetto all'operatore delete e non si preoccupa dei distruttori (è essenzialmente un po 'come free). Confrontalo con l'operatore delete, che internamente fa l'equivalente di invocare il distruttore e chiamare operator delete.

Vale la pena notare che non è necessario utilizzare ::operator new e ::operator delete per allocare e deallocare il buffer: per quanto riguarda il posizionamento nuovo, non importa in che modo il buffer viene creato/viene distrutto. Il punto principale è separare le preoccupazioni sull'allocazione della memoria e sulla durata dell'oggetto.

Per inciso, una possibile applicazione di questo sarebbe in qualcosa come un gioco, in cui si potrebbe desiderare di allocare un grande blocco di memoria in primo piano al fine di gestire attentamente l'utilizzo della memoria. Dovresti quindi costruire oggetti nella memoria che hai già acquisito.

Un altro possibile utilizzo potrebbe essere un allocatore di oggetti di piccole dimensioni e ottimizzato.

+0

+1 grazie per averlo indicato! – codekiddy

3

Probabilmente è più facile capire se si immagina di costruire diversi oggetti MyClass entro un blocco di memoria.

In tal caso, sarebbe andare qualcosa come:

  1. allocare un blocco gigante di memoria utilizzando new char [10 * sizeof (MyClass)] o malloc (10 * sizeof (MyClass))
  2. Usa il nuovo posizionamento per costruire dieci oggetti MyClass all'interno di quella memoria.
  3. Fai qualcosa.
  4. Chiama il distruttore di ciascuno dei tuoi oggetti
  5. Deallocate il grande blocco di memoria usando delete [] o free().

Questo è il genere di cosa che si potrebbe fare se sei scrivere un compilatore, o di un sistema operativo, ecc

In questo caso, spero che sia chiaro perché è necessario "distruttore" separata e "cancella" i passaggi, perché non c'è motivo per cui lo verrà cancellato dalla chiamata.Tuttavia, è deallocare la memoria, ma normalmente lo farai (libero, cancella, non fare nulla per un array statico gigante, esci normalmente se l'array fa parte di un altro oggetto, ecc. Ecc.) E se non lo fai sarà trapelato.

Inoltre, come ha detto Greg, in questo caso, non è possibile utilizzare Elimina perché è stato assegnato all'array nuovo [], quindi è necessario utilizzare delete [].

Si noti inoltre che è necessario presumere di non aver eliminato l'eliminazione per MyClass, altrimenti farà qualcosa di completamente diverso che è quasi certamente incompatibile con "nuovo".

Quindi penso che non vogliate chiamare "cancellare" come descrivete, ma potrebbe mai funzionare? Penso che questa sia fondamentalmente la stessa domanda di "Ho due tipi non correlati che non hanno distruttori. Posso aggiungere un puntatore a un tipo, quindi cancellare quella memoria attraverso un puntatore a un altro tipo?" In altre parole, "il mio compilatore ha una grande lista di tutte le cose allocate, o può fare cose diverse per tipi diversi".

Ho paura di non esserne sicuro. Leggendo le specifiche dice:

5.3.5 ... Se il tipo statico del operando [dell'operatore delete] è diverso dal suo tipo dinamico, tipo statico deve essere una classe base dell'operando del il tipo dinamico e il tipo statico devono avere un distruttore virtuale o il comportamento non è definito.

Penso che significhi "Se si usano due tipi non correlati, non funziona (è corretto cancellare un oggetto di classe in modo polimorfico attraverso un distruttore virtuale)."

Quindi no, non farlo. Ho il sospetto che possa spesso funzionare in pratica, se il compilatore cerca esclusivamente l'indirizzo e non il tipo (e nessuno dei due tipi è una classe a ereditarietà multipla, che mancherebbe l'indirizzo), ma non provarlo.

Problemi correlati