2010-03-17 10 views
7

Supponiamo di avere una mappa STL in cui i valori sono puntatori e voglio eliminarli tutti. Come rappresenterei il seguente codice, ma facendo uso di std :: for_each? Sono contento che le soluzioni utilizzino Boost.Come utilizzerei for_each per cancellare ogni valore in una mappa STL?

for(stdext::hash_map<int, Foo *>::iterator ir = myMap.begin(); 
    ir != myMap.end(); 
    ++ir) 
{ 
    delete ir->second; // delete all the (Foo *) values. 
} 

(ho trovato Boost checked_delete, ma non sono sicuro di come applicare che al pair<int, Foo *> che l'iteratore rappresenta).

(Inoltre, ai fini di questa domanda, ignorare il fatto che la memorizzazione di puntatori non elaborati che devono essere eliminati in un contenitore STL non è molto ragionevole).

Nota: in seguito ho trovato ed elencato una risposta su una riga in basso ... ma il codice è piuttosto orribile, quindi ho accettato la risposta più sicura di GMan.

+0

Uso piacevole dell'operazione di pre-elaborazione sul tuo iteratore! –

risposta

14

devi fare un oggetto funzione:

struct second_deleter 
{ 
    template <typename T> 
    void operator()(const T& pX) const 
    { 
     delete pX.second; 
    } 
}; 

std::for_each(myMap.begin(), myMap.end(), second_deleter()); 

Se stai usando spinta, si potrebbe anche utilizzare la libreria lambda:

namespace bl = boost::lambda; 
std::for_each(myMap.begin(), myMap.end(), second_deleter(), 
       bl::bind(bl::delete_ptr(), 
       bl::bind(std::select2nd<myMap::value_type>(), _1)); 

ma si potrebbe provare la libreria pointer containers che lo fa automaticamente.

Nota che non si sta utilizzando una mappa, ma hash_map. Vi consiglio di passare a boost unordered_map, che è più attuale. Tuttavia, non sembra esserci un ptr_unordered_map.

Per sicurezza, dovresti concludere questa cosa. Per esempio:

template <typename T, typename Deleter> 
struct wrapped_container 
{ 
    typedef T container_type; 
    typedef Deleter deleter_type; 

    wrapped_container(const T& pContainer) : 
    container(pContainer) 
    {} 

    ~wrapped_container(void) 
    { 
     std::for_each(container.begin(), container.end(), deleter_type()); 
    } 

    T container; 
}; 

e usarlo come:

typedef wrapped_container< 
      boost::unordered_map<int, Foo*>, second_deleter> my_container; 

my_container.container./* ... */ 

Questo assicura non importa cosa, il vostro contenitore verrà ripetuta attraverso con un deleter. (. Per le eccezioni, per esempio)

Confronta:

std::vector<int*> v; 
v.push_back(new int); 

throw "leaks!"; // nothing in vector is deleted 

wrapped_container<std::vector<int*> > v; 
v.container.push_back(new int); 

throw "no leaks!"; // wrapped_container destructs, deletes elements 
+0

Ho pensato che potesse essere qualcosa del genere. Speravo che sarebbe stato possibile farlo senza definire il mio tipo. È possibile utilizzare boost :: bind o select2nd o somesuch, mi chiedo? – stusmith

+0

non invoca tecnicamente UB se la mappa decide di riequilibrare se stesso e quindi inizia a copiare i puntatori cancellati in giro? magari annullarli anche loro? - Ho appena notato che hash_map non è una mappa quindi non riequilibrerà –

+0

@jk: questo non rimuove nulla dalla mappa, ma elimina gli oggetti a cui punta. –

0

Se è possibile, perché non si utilizza puntatori intelligenti nella mappa?

+0

Dovrebbe essere possibile a un certo punto. Al momento sto refactoring un codice piuttosto vecchio. In verità, ero solo curioso di sapere se potevo farlo in una riga di codice. Ma hai assolutamente ragione, alla fine mi piacerebbe arrivarci ... ma questo fa parte di un milione di righe di codice di 15 anni. – stusmith

+0

Vedo il tuo punto, ma l'uso di puntatori intelligenti qui elimina la necessità di refactoring e di eseguire il debug dell'eliminazione dei membri. Una gestione della memoria in meno di cui preoccuparsi di andare avanti. Ogni volta che uso new/delete penso davvero che sia necessario. Un "odore di codice" personale (secondo Martin Fowler), se vuoi. –

+0

Ovviamente, se il tuo vecchio codice restituisce una mappa, allora l'approccio 'for_each' è probabilmente la scelta migliore, ma se hai avuto qualche mano nella creazione della mappa, ti consiglio di usare i puntatori intelligenti. – Jacob

3

Hai provato a utilizzare BOOST_FOREACH? Questo dovrebbe permetterti di farlo su una linea senza creare il tuo funtore.

Non ho ancora testato il seguente codice ma dovrebbe essere simile a questa (se non proprio):

typedef stdext::hash_map<int, Foo *> MyMapType; //see comment. 
BOOST_FOREACH(MyMapType::value_type& p, myMap) 
{ 
    delete p.second; 
} 

Beh questo è più di 1 riga, a causa del typedef :)

+0

Generalmente suono. Sfortunatamente, è una macro e non può gestire le virgole tra '<>' – UncleBens

+1

ah sì, sarebbe necessario un typedef, mi dispiace, non più una riga. – Akanksh

0

OK, Ho scoperto come farlo in una riga ... ma non credo che farei mai quanto segue in codice reale!

std::for_each(mayMap.begin() 
      , myMap.end() 
      , boost::bind(&boost::checked_delete<Foo> 
          , boost::bind(&stdext::hash_map<int, Foo *>::value_type::second, _1))); 

Comunque ho intenzione di accettare la risposta di Gman perché mi piace la sua idea di un contenitore avvolto, e la mia risposta, pur essendo una linea come richiesto, è semplicemente brutto.

Problemi correlati