2016-05-01 37 views
11

Il codice seguenteUn oggetto può cancellarsi da un contenitore C++ standard?

#include <iostream> 
#include <map> 

struct foo 
{ 
    void kill(std::map<int, foo>& m, int i) 
    { 
    m.erase(i); 
    } 
}; 

int main() 
{ 
    std::map<int, foo> m; 

    m.emplace(1, foo()); 

    std::cout << m.size() << std::endl; 

    m[1].kill(m, 1); 

    std::cout << m.size() << std::endl; 
} 

compila senza preavviso (g ++), esegue senza errori e giudicare dall'uscita metodo kill cancella l'oggetto foo dalla mappa. Tuttavia, ritengo che questo potrebbe essere in realtà un comportamento indefinito. Sembra che nel metodo kill dopo la riga m.erase(i)this non punti più a un oggetto valido.

Cosa dice lo standard C++ a riguardo?

+1

Sì, è possibile. http://stackoverflow.com/questions/862093/object-delete-itself-from-container – Auriga

+1

Fondamentalmente lo stesso principio di ['delete this;'] (http://stackoverflow.com/q/3150942/2069064) – Barry

risposta

7

Quando si immette la kill, m[1] (da m[1].kill(m, 1);) dichiarazione è stata completamente valutata come l'oggetto che si sta chiamando fookill on.

Quindi si esegue m.erase(i); terminando la distruzione dell'oggetto corrente foo.

Per quanto si scrive assolutamente alcuna dichiarazione utilizzando l'oggetto corrente (this) prima di tornare dalla funzione kill, che è perfettamente accettabile e sicuro (come ha commentato dai messaggi a cui fa riferimento Auriga e Barry). Anche se l'oggetto corrente non esiste più, la tua funzione tornerà in modo sicuro dalla pila, senza alcun motivo per farlo fallire per quanto ne so.

titolo di esempio, questo finirebbe con comportamento non definito e non deve essere fatto:

struct foo 
{ 
    void kill(std::map<int, foo>& m, int i) 
    { 
    m.erase(i); 
    cout << attribute; // don't do that! current foo object does not exist anymore 
    } 
    int attribute; 
}; 

Quindi diciamo che quello che stai facendo è rischioso, ma valido e sicuro se lo fai bene.

titolo di esempio, questo finirebbe con comportamento definito e può essere fatto:

struct foo 
{ 
    void kill(std::map<int, foo>& m, int i) 
    { 
    int theAttribute = attribute; 
    m.erase(i); 
    cout << theAttribute; // OK! 
    } 
    int attribute; 
}; 

Avere un metodo eliminare l'oggetto corrente non è probabilmente una buona pratica in ogni caso (specialmente se un altro sviluppatore modifica il codice in seguito ... potrebbe facilmente farlo andare in crash con il primo esempio sopra). Almeno porre commento esplicito nel codice per dire l'oggetto corrente avrebbero potuto essere distrutte (si noti che kill potrebbe distruggere l'oggetto corrente, un altro, o nessuno ... a seconda m contenuti e i):

struct foo 
{ 
    void kill(std::map<int, foo>& m, int i) 
    { 
    m.erase(i); 
    // careful! current object could have been destroyed by above statement and may not be valid anymore! Don't use it anymore! 
    } 
}; 
-4

Questo non è affatto sicuro. m.erase(i) eviterà di provare a cancellare lo stesso oggetto se chiamato più volte, ma m[1].kill(m, 1) è un comportamento non definito se chiamato più di una volta. Per essere leggermente più sicuro, m.at(1).kill(m, 1) genererà un errore out_of_range.

+0

Is la mia risposta è sbagliata o le persone sono semplicemente felici? – user6279021

+0

@StoryTeller Il comportamento non definito è un comportamento non definito. Illustra che 'm [1]' non è più valido. – user6279021

+2

Non è nemmeno un comportamento indefinito se chiamato più di una volta ... 'm [1]' dovrebbe prima inserire un nuovo oggetto, che verrebbe immediatamente cancellato. – Barry