2009-06-24 12 views
25

Supuestamente no se puede simplemente borrar/eliminar un elemento en un contenedor mientras se itera cuando el iterador deja de ser válido. ¿Cuáles son las formas (seguras) de eliminar los elementos que cumplen una determinada condición? por favor solo stl, no boost o tr1.Borrar/Eliminar el contenido del mapa (o cualquier otro contenedor STL) mientras lo itera

EDITAR ¿Existe una forma más elegante si quiero borrar un número de elementos que cumplen un cierto criterio, tal vez con el uso de funtor y for_each o borrar algoritmo?

Respuesta

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

¿qué es bind1st? – 0xC0DEFACE

+0

bind1st crea una función como objeto que esencialmente le da una llamada de función con un primer parámetro constante, por lo que en el ejemplo tendría el efecto de equal_to (4, X) donde X proviene de la secuencia sobre la que estamos iterando. El efecto es que cada valor en la secuencia se compara con 4. – markh44

9

Ejemplo 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

¿No sabía sobre eso, pero no es el iterador devuelto de borrar uno nuevo, validado? ¿Suena muy extraño que devuelva un iterador no válido? –

+2

¿Cuál sería el punto de devolver un iterador? –

+7

@j_random_hacker: tiene razón en que invalida cualquier iterador ... pero std :: vector :: erase devuelve un iterador * nuevo *, * válido * al elemento después del borrado (o final). Este código es perfectamente válido. –

31

Puede todo el tiempo que no invalidan su iterador después de haber borrado:

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

+1. "myContainer.erase (it ++);" es sutil: realiza correctamente el incremento * antes * de llamar a erase(), cuando aún es válido para hacerlo, mientras pasa (una copia de) el iterador * no incrementado * a esa función. –

+16

IMPORTANTE: ese código funciona para el mapa, el conjunto y la lista, pero NO funcionará para el vector; el borrado de un vector invalida los iteradores para ese y todos los elementos subsiguientes (23.2.4.3/3). He dejado caer mi +1 por ahora, será re + 1 cuando menciones esto. –

+0

¿Está seguro de que el incremento es antes de llamar a borrar? AFAIK se garantiza que el incremento se realizará antes del final de la oración, pero el orden correcto es específico de la implementación. Por ejemplo, un gnu en modo de depuración hace algo como esto 'v.erase (it), it ++', pero en modo de lanzamiento hace '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

Sin embargo, no funciona con un mapa. –

+1

ahora lo hace ... – TimW

+0

La versión del mapa intenta incrementar un iterador después de haber sido borrado (y por lo tanto no es válido). – interjay

2

I prefiera la versión 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 no hay peligro de incrementar it dos veces como podría ser en for bucle.

1

markh44 es la respuesta más STL-ish. Sin embargo, tenga en cuenta que, en general, los iteradores se invalidan al modificar el contenedor, pero establecer y asignar son excepciones. Allí, puede eliminar elementos y seguir usando los iteradores, excepto si elimina el elemento al que hace referencia su iterador.

3

La solución de Viktor tiene la ventaja de poder hacer algo con el elemento antes de eliminarlo. (Yo no era capaz de hacer esto con remove_if o remove_copy_if.) Pero yo prefiero usar std::find_if por lo que nunca tenga que incrementar el iterador a mí mismo:

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 
} 

predicado WHERE podría ser bind1st(equal_to<int>(), 4) o menos así:

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

Utilice el hecho de que el operador de decremento posterior devuelve una copia del iterador antes de que disminuya. Debido a que el iterador decrementado sigue siendo válido después de borrar el elemento actual, el bucle for continúa operando según lo 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; 
} 

Editar: No funciona si se intenta borrar el primer elemento en la lista ....

2

1.Para std::vector<>:

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

2. Para utilizar siempre std::map<>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 std::list::erase()
Cuestiones relacionadas