2009-06-24 8 views
25

Presumibilmente non è possibile cancellare/rimuovere un elemento in un contenitore mentre iterazione come iteratore diventa non valido. Quali sono i modi (sicuri) per rimuovere gli elementi che soddisfano una determinata condizione? per favore solo stl, no boost o tr1.Cancella/Rimuove il contenuto dalla mappa (o qualsiasi altro contenitore STL) mentre lo itera

EDIT C'è un modo più elegante se voglio cancellare un numero di elementi che soddisfano determinati criteri, forse con l'utilizzo di funtore e for_each o cancellare algoritmo?

risposta

9
bool IsOdd(int i) 
{ 
    return (i&1)!=0; 
} 

int a[] = {1,2,3,4,5}; 
vector<int> v(a, a + 5); 
v.erase(remove_if(v.begin(), v.end(), bind1st(equal_to<int>(), 4)), v.end()); 
// v contains {1,2,3,5} 
v.erase(remove_if(v.begin(), v.end(), IsOdd), v.end()); 
// v contains {2} 
+0

che cosa è bind1st? – 0xC0DEFACE

+0

bind1st crea una funzione come oggetto che essenzialmente ti dà una chiamata di funzione con un primo parametro costante - quindi nell'esempio avrebbe l'effetto di uguale a (4, X) dove X viene dalla sequenza su cui stiamo iterando. L'effetto è che ogni valore nella sequenza viene confrontato con 4. – markh44

9

Esempio con std :: vector

#include <vector> 

using namespace std; 

int main() 
{ 

    typedef vector <int> int_vector; 

    int_vector v(10); 

    // Fill as: 0,1,2,0,1,2 etc 
    for (size_t i = 0; i < v.size(); ++i){ 
     v[i] = i % 3; 
    } 

    // Remove every element where value == 1  
    for (int_vector::iterator it = v.begin(); it != v.end(); /* BLANK */){ 
     if (*it == 1){ 
     it = v.erase(it); 
     } else { 
     ++it; 
     } 
    } 

} 
+1

Non lo sapevo, ma l'iteratore non ha restituito un nuovo, convalidato? Sembra strano che restituisca un iteratore non valido? –

+2

Quale sarebbe altrimenti il ​​punto di restituzione di un iteratore? –

+7

@j_random_hacker: hai ragione che invalida qualsiasi iteratore ... ma std :: vector :: erase restituisce un * nuovo *, * valido * iteratore all'elemento dopo quello cancellato (o alla fine). Questo codice è perfettamente valido. –

31

È possibile finché non si invalida l'iteratore dopo aver cancellato esso:

MyContainer::iterator it = myContainer.begin(); 
while(it != myContainer.end()) 
{ 
    if (*it == matchingValue) 
    { 
     myContainer.erase(it++); 
    } 
    else 
    { 
     ++it; 
    } 
} 
+15

+1. "MyContainer.erase (it ++);" è sottile - esegue correttamente l'incremento * prima * chiamando cancella(), quando è ancora valido farlo, mentre passa (una copia di) * iteratore * nonincrementato a quella funzione. –

+16

IMPORTANTE: quel codice funziona per mappa, set ed elenco, ma NON funzionerà per il vettore - la cancellazione da un vettore invalida gli iteratori a questo e tutti gli elementi successivi (23.2.4.3/3). Ho lasciato cadere il mio +1 per ora, re + 1 quando lo menzioni. –

+0

Sei sicuro che l'incremento sia prima di chiamare cancellare? AFAIK è garantito che l'incremento venga eseguito prima della fine della frase, ma l'ordine corretto è specifico dell'implementazione. Ad esempio un gnu in modalità debug fa qualcosa del tipo 'v.erase (it), esso ++', ma in modalità rilascio fa 'tmp = it, ++ it, v.erase (tmp)'. – Ismael

2
template <class Container, class Predicate> 
void eraseIf(Container& container, Predicate predicate ) { 
    container.erase(remove_if(container.begin(), container.end(), predicate), container.end()); 
} 

template<class K, class V, class Predicate> 
void eraseIf(map<K,V>& container, Predicate predicate ) { 
    for(typename map<K,V>::iterator iter=container.begin() ; iter!=container.end() ; ++iter) { 
     if(predicate(iter)) 
      container.erase(iter); 
    } 
} 
+1

Tuttavia, non funziona con una mappa. –

+1

adesso ... – TimW

+0

La versione della mappa tenta di incrementare un iteratore dopo che è stato cancellato (e quindi non è valido). – interjay

2

I preferisco la versione con while:

typedef std::list<some_class_t> list_t; 
void f(void) { 
    // Remove items from list 
    list_t::iterator it = sample_list.begin(); 
    while (it != sample_list.end()) { 
    if (it->condition == true) { 
     it = sample_list.erase(it); 
    } else ++it;  
    } 
} 

Con while non c'è alcun pericolo di incrementare it due volte come potrebbe essere nel ciclo for.

1

markh44 è la risposta più STL-ish. Nota, tuttavia, che in generale, gli iteratori vengono invalidati modificando il contenitore, ma set e map sono eccezioni. Lì, puoi rimuovere gli oggetti e continuare a utilizzare gli iteratori, a meno che non elimini proprio l'elemento a cui fa riferimento l'iteratore.

3

La soluzione di Viktor ha il vantaggio di poter fare qualcosa con l'elemento prima di rimuoverlo. (Non ero in grado di fare questo con remove_if o remove_copy_if.) Ma io preferisco usare std::find_if così non ho mai dovuto incrementare l'iteratore me stesso:

typedef vector<int> int_vector; 
int_vector v; 

int_vector::iterator itr = v.begin(); 
for(;;) 
{ 
    itr = std::find_if(itr, v.end(), Predicate(4)); 
    if (itr == v.end()) 
    { 
     break; 
    } 

    // do stuff with *itr here 

    itr = v.erase(itr); // grab a new, valid iterator 
} 

Dove predicato potrebbe essere bind1st(equal_to<int>(), 4) o qualcosa di simile:

struct Predicate : public unary_function<int, bool> 
{ 
    int mExpected; 
    Predicate(int desired) : mExpected(desired) {} 
    bool operator() (int input) 
    { 
     return (input == mExpected); 
    } 
}; 
1

Utilizzare il fatto che l'operatore di post-decremento restituisce una copia dell'iteratore prima dello che decrementa. Poiché l'iteratore decrementato è ancora valido dopo aver cancellato l'elemento corrente, il ciclo for continua a funzionare come previsto.

#include <list> 
std::list<int> myList; 
for(int i = 0; i < 10; ++i) 
{ 
    myList.push_back(i); 
} 

int cnt = 0; 
for(std::list<int>::iterator iter = myList.begin(); iter != myList.end(); ++iter) 
{ 
    if(cnt == 5) 
    { 
     myList.erase(iter--); 
    } 
    ++cnt; 
} 

Edit: Non funziona se si tenta di cancellare il primo elemento della lista ....

2

1.Per std::vector<>:

std::vector <int> vec; 
vec.erase(std::remove(vec.begin(),vec.end(), elem_to_remove), vec.end()); 

2.Per std::map<> usare sempre std::map::erase()

std::map<int,std::string> myMap; 
myMap.emplace(std::make_pair(1, "Hello")); 
myMap.emplace(std::make_pair(2, "Hi")); 
myMap.emplace(std::make_pair(3, "How")); 
myMap.erase(1);//Erase with key 
myMap.erase(myMap.begin(), ++myMap.begin());//Erase with range 
for(auto &ele: myMap) 
{ 
    if(ele.first ==1) 
    { 
     myMap.erase(ele.first);//erase by key 
     break; //You can't use ele again properly 
       //wthin this iteration, so break. 
    } 
} 
  1. Fo r std::list use
Problemi correlati