Pregunta ¿Quitar el elemento del vector, mientras que en el rango de C ++ 11 'for' loop?


Tengo un vector de IInventory *, y estoy recorriendo la lista usando el rango de C ++ 11, para hacer cosas con cada uno.

Después de hacer algunas cosas con una, es posible que desee eliminarlo de la lista y eliminar el objeto. Sé que puedo llamar delete en el puntero en cualquier momento para limpiarlo, pero ¿cuál es la forma correcta de eliminarlo del vector, mientras está en el rango for ¿lazo? Y si lo elimino de la lista ¿mi bucle será invalidado?

std::vector<IInventory*> inv;
inv.push_back(new Foo());
inv.push_back(new Bar());

for (IInventory* index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
}

76
2018-04-28 04:00


origen


Respuestas:


No, no puedes. Basado en rango for es para cuando necesita acceder a cada elemento de un contenedor una vez.

Deberías usar la normal for loop o uno de sus primos si necesita modificar el contenedor a medida que avanza, acceder a un elemento más de una vez o iterar de forma no lineal a través del contenedor.

Por ejemplo:

auto i = std::begin(inv);

while (i != std::end(inv)) {
    // Do some stuff
    if (blah)
        i = inv.erase(i);
    else
        ++i;
}

71
2018-04-28 04:02



Cada vez que se elimina un elemento del vector, debe asumir que los iteradores en o después del elemento borrado ya no son válidos, porque cada uno de los elementos que suceden al elemento borrado se mueve.

Un bucle for basado en el rango es solo azúcar sintáctica para bucle "normal" usando iteradores, por lo que se aplica lo anterior.

Dicho esto, podrías simplemente:

inv.erase(
    std::remove_if(
        inv.begin(),
        inv.end(),
        [](IInventory* element) -> bool {
            // Do "some stuff", then return true if element should be removed.
            return true;
        }
    ),
    inv.end()
);

45
2018-04-28 04:34



Lo ideal es que no modifiques el vector mientras lo iteras. Usa el idioma borrar-eliminar. Si lo haces, es probable que encuentres algunos problemas. Como en un vector un erase invalida todos los iteradores comenzando con el elemento borrado hasta el end() Deberá asegurarse de que sus iteradores sigan siendo válidos mediante:

for (MyVector::iterator b = v.begin(); b != v.end();) { 
    if (foo) {
       b = v.erase( b ); // reseat iterator to a valid value post-erase
    else {
       ++b;
    }
}

Tenga en cuenta que necesita el b != v.end() prueba tal como es. Si intenta optimizarlo de la siguiente manera:

for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)

te toparás con UB ya que tu e se invalida después de la primera erase llamada.


10
2018-04-28 04:31



¿Es un requisito estricto eliminar elementos mientras está en ese bucle? De lo contrario, podría establecer los punteros que desea eliminar en NULL y hacer otra pasada sobre el vector para eliminar todos los punteros NULL.

std::vector<IInventory*> inv;
inv.push_back( new Foo() );
inv.push_back( new Bar() );

for ( IInventory* &index : inv )
{
    // do some stuff
    // ok I decided I need to remove this object from inv...?
    if (do_delete_index)
    {
        delete index;
        index = NULL;
    }
}
std::remove(inv.begin(), inv.end(), NULL);

5
2018-04-28 22:18



lo siento por necroposting y también lo siento si mi experiencia en c ++ se interpone en el camino de mi respuesta, pero si tratas de iterar a través de cada elemento y hacer posibles cambios (como borrar un índice), intenta usar un backwords for loop.

for(int x=vector.getsize(); x>0; x--){

//do stuff
//erase index x

}

al borrar el índice x, el siguiente ciclo será para el ítem "al frente" de la última iteración. realmente espero que esto haya ayudado a alguien


1
2018-05-25 20:09



OK, llego tarde, pero de todos modos: Lo siento, no corrijo lo que leí hasta ahora - es posible, solo necesitas dos iteradores:

std::vector<IInventory*>::iterator current = inv.begin();
for (IInventory* index : inv)
{
    if(/* ... */)
    {
        delete index;
    }
    else
    {
        *current++ = index;
    }
}
inv.erase(current, inv.end());

Solo modificar el valor al que apunta un iterador no invalida ningún otro iterador, por lo que podemos hacerlo sin tener que preocuparnos. Actualmente, std::remove_if (la implementación de gcc al menos) hace algo muy similar (usando un loop clásico ...), simplemente no borra nada y no borra.

Tenga en cuenta, sin embargo, que esto no es seguro para subprocesos (!); Sin embargo, esto también se aplica a algunas de las otras soluciones anteriores ...


1
2018-03-15 12:29



Voy a mostrar con el ejemplo, el siguiente ejemplo eliminar elementos extraños del vector:

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};

    //method 1
    for(auto it = vecInt.begin();it != vecInt.end();){
        if(*it % 2){// remove all the odds
            it = vecInt.erase(it);
        } else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 2
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 2
    for(auto it=std::begin(vecInt);it!=std::end(vecInt);){
        if (*it % 2){
            it = vecInt.erase(it);
        }else{
            ++it;
        }
    }

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

    // recreate vecInt, and use method 3
    vecInt = {0, 1, 2, 3, 4, 5};
    //method 3
    vecInt.erase(std::remove_if(vecInt.begin(), vecInt.end(),
                 [](const int a){return a % 2;}),
                 vecInt.end());

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;

}

salida aw a continuación:

024
024
024

Tenga en cuenta que el método erase devolverá el siguiente iterador del iterador pasado.

De aquí , podemos usar un método más generate:

template<class Container, class F>
void erase_where(Container& c, F&& f)
{
    c.erase(std::remove_if(c.begin(), c.end(),std::forward<F>(f)),
            c.end());
}

void test_del_vector(){
    std::vector<int> vecInt{0, 1, 2, 3, 4, 5};
    //method 4
    auto is_odd = [](int x){return x % 2;};
    erase_where(vecInt, is_odd);

    // output all the remaining elements
    for(auto const& it:vecInt)std::cout<<it;
    std::cout<<std::endl;    
}

Vea aquí para ver cómo usar std::remove_if. https://en.cppreference.com/w/cpp/algorithm/remove


1
2017-07-29 01:56