Cancella questo permesso?


232

È consentito delete this;se l'istruzione delete è l'ultima istruzione che verrà eseguita su quell'istanza della classe? Ovviamente sono sicuro che l'oggetto rappresentato dal this-pointer sia newcreato.

Sto pensando a qualcosa del genere:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Posso farlo?


13
Il problema principale sarebbe che se hai delete thiscreato un accoppiamento stretto tra la classe e il metodo di allocazione usato per creare oggetti di quella classe. Questo è un progetto OO molto scadente, poiché la cosa più fondamentale in OOP è creare classi autonome che non conoscono o si preoccupano di ciò che il loro chiamante sta facendo. Pertanto, una classe progettata correttamente non dovrebbe conoscere o preoccuparsi di come è stata assegnata. Se per qualche motivo hai bisogno di un meccanismo così particolare, penso che un progetto migliore sarebbe usare una classe wrapper attorno alla classe reale e lasciare che il wrapper gestisca l'allocazione.
Lundin,

Non riesci a cancellare in setWorkingModule?
Jimmy T.,

Risposte:


238

Le FAQ di C ++ Lite hanno una voce specifica per questo

Penso che questa citazione la riassuma bene

Finché stai attento, va bene che un oggetto si suicida (eliminalo).


15
L'FQA corrispondente contiene anche alcuni commenti utili: yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C.

1
Per motivi di sicurezza, è possibile utilizzare il distruttore privato sull'oggetto originale per assicurarsi che non sia costruito nello stack o come parte di un array o vettore.
Cem Kalyoncu,

Definisci "attento"
CinCout il

3
"Attento" è definito nell'articolo FAQ collegato. (Mentre il collegamento FQA è in gran parte diffuso - come quasi tutto in esso - quanto è brutto C ++)
CharonX

88

Sì, delete this;ha definito i risultati, purché (come hai notato) assicuri che l'oggetto sia stato allocato in modo dinamico e (ovviamente) non cerchi mai di utilizzare l'oggetto dopo che è stato distrutto. Nel corso degli anni, sono state poste molte domande su ciò che lo standard dice specificamente delete this;, invece di eliminare qualche altro indicatore. La risposta è piuttosto breve e semplice: non dice molto. Dice solo che deletel'operando deve essere un'espressione che designa un puntatore a un oggetto o una matrice di oggetti. Indica un po 'di dettagli su cose come il modo in cui determina quale (se presente) funzione di deallocazione chiamare per rilasciare la memoria, ma l'intera sezione su delete(§ [expr.delete]) non menziona affatto in modo delete this;specifico. La sezione sui distruttori menzionadelete this in un unico posto (§ [class.dtor] / 13):

Nel punto di definizione di un distruttore virtuale (inclusa una definizione implicita (15.8)), la funzione di deallocazione non array viene determinata come se per l'espressione cancellasse questo che appare in un distruttore non virtuale della classe del distruttore (vedere 8.3.5 ).

Ciò tende a sostenere l'idea che lo standard considera delete this;valido - se non fosse valido, il suo tipo non sarebbe significativo. Questo è l'unico posto menzionato dalla norma delete this;, per quanto ne so.

Ad ogni modo, alcuni considerano delete thisun brutto hack e dicono a chiunque ascolterà che dovrebbe essere evitato. Un problema comunemente citato è la difficoltà di garantire che gli oggetti della classe vengano allocati dinamicamente e sempre. Altri lo considerano un linguaggio perfettamente ragionevole e lo usano sempre. Personalmente, sono da qualche parte nel mezzo: lo uso raramente, ma non esitate a farlo quando sembra essere lo strumento giusto per il lavoro.

La prima volta che usi questa tecnica è con un oggetto che ha una vita quasi interamente sua. Un esempio citato da James Kanze è stato un sistema di fatturazione / localizzazione su cui ha lavorato per una compagnia telefonica. Quando inizi a fare una telefonata, qualcosa ne prende atto e crea un phone_calloggetto. Da quel momento in poi, l' phone_calloggetto gestisce i dettagli della telefonata (effettuare una connessione quando si compone il numero, aggiungere una voce al database per dire quando è iniziata la chiamata, possibilmente connettere più persone se si fa una chiamata in conferenza, ecc.) Quando le ultime persone sulla chiamata riagganciano, l' phone_calloggetto fa la sua contabilità finale (ad esempio, aggiunge una voce al database per dire quando hai riattaccato, in modo che possano calcolare quanto tempo è stata la tua chiamata) e quindi si distrugge. La vita diphone_calll'oggetto si basa su quando la prima persona inizia la chiamata e quando l'ultima persona lascia la chiamata - dal punto di vista del resto del sistema, è sostanzialmente del tutto arbitraria, quindi non puoi collegarla a nessun ambito lessicale nel codice o qualsiasi cosa in quell'ordine.

Per chiunque possa preoccuparsi di quanto possa essere affidabile questo tipo di codifica: se fai una telefonata a, da o attraverso quasi ogni parte d'Europa, c'è una buona possibilità che venga gestita (almeno in parte) dal codice questo fa esattamente questo.


2
Grazie, lo metterò da qualche parte nella mia memoria. Suppongo che tu definisca i costruttori e i distruttori come privati ​​e usi un metodo statico di fabbrica per creare tali oggetti.
Alexandre C.,

@Alexandre: Probabilmente lo faresti nella maggior parte dei casi - non so da nessuna parte vicino a tutti i dettagli del sistema su cui stava lavorando, quindi non posso dirlo con certezza.
Jerry Coffin,

Il modo in cui spesso aggiro il problema di come è stata allocata la memoria è di includere un bool selfDeleteparametro nel costruttore che viene assegnato a una variabile membro. Certo, questo significa consegnare al programmatore una corda sufficiente per legare un cappio, ma trovo che sia preferibile alle perdite di memoria.
MBraedley,

1
@MBraedley: ho fatto lo stesso, ma preferisco evitare quello che mi sembra un kludge.
Jerry Coffin,

Per chiunque possa interessarsene ... ci sono buone possibilità che venga gestito (almeno in parte) da un codice che fa esattamente this. Sì, il codice viene gestito esattamente da this. ;)
Galaxy

46

Se ti spaventa, c'è un trucco perfettamente legale:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Penso che delete thissia C ++ idiomatico, e lo presento solo come una curiosità.

Esiste un caso in cui questo costrutto è effettivamente utile: è possibile eliminare l'oggetto dopo aver generato un'eccezione che necessita dei dati dei membri dall'oggetto. L'oggetto rimane valido fino al termine del lancio.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Nota: se stai usando un compilatore più vecchio di C ++ 11 che puoi usare std::auto_ptrinvece di std::unique_ptr, farà la stessa cosa.


Non riesco a compilare questo usando c ++ 11, ci sono alcune opzioni di compilatore speciali per questo? Inoltre non richiede uno spostamento di questo puntatore?
Gufo

@ Gufo non so cosa intendi, funziona per me: ideone.com/aavQUK . La creazione di un unique_ptrda un altro unique_ptr richiede una mossa, ma non da un puntatore crudo. A meno che le cose non siano cambiate in C ++ 17?
Mark Ransom,

Ahh C ++ 14, questo sarà il motivo. Devo aggiornare il mio c ++ sulla mia casella di sviluppo. Ci riproverò stasera sul mio sistema gentoo recentemente emerso!
Gufo

25

Uno dei motivi per cui è stato progettato C ++ è stato quello di semplificare il riutilizzo del codice. In generale, C ++ deve essere scritto in modo che funzioni indipendentemente dal fatto che la classe sia istanziata nell'heap, in un array o nello stack. "Elimina questo" è una pessima pratica di codifica perché funzionerà solo se una singola istanza è definita sull'heap; e sarebbe meglio che non ci fosse un'altra dichiarazione di eliminazione, che viene generalmente utilizzata dalla maggior parte degli sviluppatori per ripulire l'heap. In questo modo si presuppone che nessun programmatore di manutenzione in futuro curerà una perdita di memoria percepita erroneamente aggiungendo un'istruzione di eliminazione.

Anche se sai in anticipo che il tuo piano attuale è quello di allocare una sola istanza sull'heap, cosa succederebbe se in futuro arrivasse qualche sviluppatore fortunato e decida di creare un'istanza nello stack? Oppure, se taglia e incolla determinate parti della classe in una nuova classe che intende utilizzare in pila? Quando il codice raggiunge "elimina questo", si spegne ed elimina, ma quando l'oggetto esce dall'ambito, chiamerà il distruttore. Il distruttore proverà quindi a eliminarlo di nuovo e quindi verrà eliminato. In passato, fare qualcosa del genere avrebbe rovinato non solo il programma, ma anche il sistema operativo e il computer avrebbe dovuto essere riavviato. In ogni caso, questo NON è altamente raccomandato e dovrebbe quasi sempre essere evitato. Dovrei essere disperato, seriamente intonacato,


7
+1. Non riesco a capire perché sei stato sottoposto a downgrade. "Il C ++ deve essere scritto in modo che funzioni indipendentemente dal fatto che la classe sia istanziata nell'heap, in un array o nello stack" è un ottimo consiglio.
Joh,

1
Potresti semplicemente avvolgere l'oggetto che desideri eliminare se stesso in una classe speciale che elimina l'oggetto e quindi se stesso e utilizzare questa tecnica per impedire l'allocazione dello stack: stackoverflow.com/questions/124880/… Ci sono momenti in cui non c'è davvero un Alternativa praticabile. Ho appena usato questa tecnica per eliminare automaticamente un thread avviato da una funzione DLL, ma la funzione DLL deve tornare prima della fine del thread.
Felix Dombek il

Non puoi programmare in modo tale che qualcuno che fa semplicemente copia e incolla con il tuo codice finisca comunque per abusarne
Jimmy T.

22

È permesso (semplicemente non usare l'oggetto dopo quello), ma non scriverei questo codice in pratica. Penso che delete thisdovrebbe apparire solo nelle funzioni che ha chiamato releaseo Releasee si presenta come: void release() { ref--; if (ref<1) delete this; }.


Che è esattamente una volta in ogni mio progetto ... :-)
cmaster - reintegrare monica il

15

Bene, in Component Object Model (COM) la delete thiscostruzione può essere una parte del Releasemetodo che viene chiamato ogni volta che si desidera rilasciare un oggetto acquisitato:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}

8

Questo è il linguaggio base per gli oggetti contati con riferimento.

Il conteggio dei riferimenti è una forma forte di raccolta dei rifiuti deterministica: assicura che gli oggetti gestiscano la loro PROPRIA vita invece di fare affidamento su puntatori "intelligenti", ecc. Per farlo. L'oggetto sottostante è sempre accessibile solo tramite i puntatori intelligenti "Riferimento", progettati in modo tale che i puntatori aumentino e diminuiscano un numero intero membro (il conteggio dei riferimenti) nell'oggetto reale.

Quando l'ultimo riferimento scende dallo stack o viene eliminato, il conteggio dei riferimenti andrà a zero. Il comportamento predefinito del tuo oggetto sarà quindi una chiamata per "eliminare questo" per la garbage collection: le librerie che scrivo forniscono una chiamata "CountIsZero" virtuale protetta nella classe base in modo da poter sovrascrivere questo comportamento per cose come la cache.

La chiave per rendere questo sicuro non è consentire agli utenti di accedere al COSTRUTTORE dell'oggetto in questione (renderlo protetto), ma piuttosto farli chiamare alcuni membri statici, come "static Reference CreateT (...)" di FABBRICA. In questo modo saprai per certo che sono sempre costruiti con un "nuovo" normale e che nessun puntatore non è mai disponibile, quindi "elimina questo" non farà mai esplodere.


Perché non puoi semplicemente avere un "allocatore / garbage collector" di classe (singleton), un'interfaccia attraverso la quale viene eseguita tutta l'allocazione e lasciare che quella classe gestisca tutto il conteggio dei riferimenti degli oggetti allocati? Piuttosto che forzare gli oggetti stessi a preoccuparsi delle attività di raccolta dei rifiuti, qualcosa che è completamente estraneo al loro scopo designato.
Lundin,

1
Puoi anche solo proteggere il distruttore per proibire allocazioni statiche e impilate del tuo oggetto.
Game_Overture,

7

Puoi farlo. Tuttavia, non puoi assegnarlo a questo. Pertanto, la ragione per cui affermi di fare questo "Voglio cambiare la visione" sembra molto discutibile. Il metodo migliore, a mio avviso, sarebbe che l'oggetto che detiene la vista sostituisca quella vista.

Certo, stai usando oggetti RAII e quindi non hai bisogno di chiamare elimina affatto ... giusto?


4

Questa è una domanda vecchia, con risposta, ma @Alexandre ha chiesto "Perché qualcuno dovrebbe voler fare questo?", E ho pensato che potrei fornire un esempio di utilizzo che sto prendendo in considerazione questo pomeriggio.

Codice legacy. Utilizza puntatori nudi Obj * obj con un obj di eliminazione alla fine.

Purtroppo a volte ho bisogno, non spesso, di mantenere in vita l'oggetto più a lungo.

Sto pensando di renderlo un riferimento contato puntatore intelligente. Ma ci sarebbe molto codice da cambiare, se dovessi usarlo ref_cnt_ptr<Obj>ovunque. E se mischi Obj * nudo e ref_cnt_ptr, puoi ottenere l'oggetto implicitamente cancellato quando l'ultimo ref_cnt_ptr scompare, anche se Obj * è ancora vivo.

Quindi sto pensando di creare un esplicito_delete_ref_cnt_ptr. Vale a dire un puntatore conteggiato riferimento in cui l'eliminazione viene eseguita solo in una routine di eliminazione esplicita. Usandolo nel punto in cui il codice esistente conosce la durata dell'oggetto, così come nel mio nuovo codice che mantiene l'oggetto in vita più a lungo.

Incrementare e decrementare il conteggio dei riferimenti come esplicito_delete_ref_cnt_ptr vengono manipolati.

Ma NON liberare quando il conteggio dei riferimenti è pari a zero nel distruttore esplicito_delete_ref_cnt_ptr.

Liberazione solo quando si considera che il conteggio dei riferimenti è zero in un'operazione esplicita simile all'eliminazione. Ad esempio in qualcosa del tipo:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

OK, qualcosa del genere. È un po 'insolito avere un tipo di puntatore contato di riferimento che non cancella automaticamente l'oggetto puntato nel distruttore pc rc. Ma sembra che questo potrebbe rendere un po 'più sicuro il mix di puntatori nudi e puntatori RC.

Ma finora non è necessario eliminarlo.

Ma poi mi è venuto in mente: se l'oggetto indicato, la punta, sa che viene conteggiato il riferimento, ad esempio se il conteggio è all'interno dell'oggetto (o in qualche altra tabella), allora la routine delete_if_rc0 potrebbe essere un metodo del oggetto punteggiato, non il puntatore (intelligente).

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

In realtà, non è necessario che sia un metodo membro, ma potrebbe essere una funzione gratuita:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(A proposito, so che il codice non è del tutto corretto - diventa meno leggibile se aggiungo tutti i dettagli, quindi lo lascio così.)


0

Elimina questo è legale fintanto che l'oggetto è in heap. Dovresti richiedere che l'oggetto sia solo heap. L'unico modo per farlo è proteggere il distruttore: in questo modo l'eliminazione può essere chiamata SOLO dalla classe, quindi è necessario un metodo che assicuri l'eliminazione

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.