Удаление элемента из вектора, а в цикле С++ 11 - для цикла?

У меня есть вектор IInventory *, и я перебираю список, используя диапазон С++ 11, чтобы делать вещи с каждым.

После выполнения некоторых действий с одним я могу удалить его из списка и удалить объект. Я знаю, что я могу называть delete на указателе в любое время, чтобы очистить его, но каков правильный способ удалить его из вектора, а в цикле диапазона for? И если я удалю его из списка, моя петля будет признана недействительной?

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'...
}

Ответ 1

Нет, вы не можете. Диапазон for на основе диапазона - это когда вам нужно получить доступ к каждому элементу контейнера один раз.

Вы должны использовать обычный цикл for или один из его кузенов, если вам нужно изменить контейнер, когда вы идете вперед, получить доступ к элементу более одного раза или иначе итератировать нелинейным способом через контейнер.

Например:

auto i = std::begin(inv);

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

Ответ 2

Каждый раз, когда элемент удаляется из вектора, вы должны предполагать, что итераторы в или после стираемого элемента уже недействительны, потому что каждый из элементов, удаляющих стертый элемент, перемещается.

Цикл for-loop, основанный на диапазоне, представляет собой просто синтаксический сахар для "нормального" цикла с использованием итераторов, поэтому применяется вышеописанное.

Сказав это, вы можете просто:

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()
);

Ответ 3

В идеале вы не должны изменять вектор, итерации по нему. Используйте стирание-удалить идиому. Если вы это сделаете, вы, вероятно, столкнетесь с несколькими проблемами. Поскольку в vector an erase аннулирует все итераторы, начиная с стираемого элемента до end(), вам нужно убедиться, что ваши итераторы остаются действительными, используя:

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;
    }
}

Обратите внимание, что вам нужен тест b != v.end() как есть. Если вы попытаетесь оптимизировать его следующим образом:

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

вы столкнетесь с UB, так как ваш e недействителен после первого вызова erase.

Ответ 4

Является ли это строгим требованием для удаления элементов в этом цикле? В противном случае вы можете установить указатели, которые хотите удалить, в NULL и сделать еще один проход над вектором, чтобы удалить все указатели 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

Хорошо, я опоздала, но все равно: К сожалению, не исправить то, что я читал до сих пор - возможно, вам просто нужно два итератора:

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

Простое изменение значения, на которое указывает итератор, не лишает законной силы любой другой итератор, поэтому мы можем сделать это, не беспокоясь. На самом деле std::remove_if (по крайней мере, реализация gcc) делает нечто очень похожее (используя классический цикл...), просто ничего не удаляет и не стирает.

Имейте в виду, однако, что это не потокобезопасно (!) - однако, это относится и к некоторым другим решениям выше...

Ответ 6

извините за некропостинг, а также извините, если моя экспертиза на С++ мешает моему ответу, но если вы пытаетесь выполнить итерацию каждого элемента и внести возможные изменения (например, удаление индекса), попробуйте использовать backwords for loop.

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

//do stuff
//erase index x

}

при стирании индекса x следующий цикл будет для элемента "перед" последней итерацией. я действительно надеюсь, что это помогло кому-то

Ответ 7

Я покажу на примере, приведенный ниже пример удаления нечетных элементов из вектора:

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;

}

вывод aw ниже:

024
024
024

Помните, что метод erase вернет следующий итератор переданного итератора.

Отсюда мы можем использовать более генерирующий метод:

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;    
}

Смотрите здесь, чтобы увидеть, как использовать std::remove_if. https://en.cppreference.com/w/cpp/algorithm/remove

Ответ 8

В отличие от этого заголовка темы, я бы использовал два прохода:

#include <algorithm>
#include <vector>

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

std::vector<IInventory*> toDelete;

for (IInventory* index : inv)
{
    // Do some stuff
    if (deleteConditionTrue)
    {
        toDelete.push_back(index);
    }
}

for (IInventory* index : toDelete)
{
    inv.erase(std::remove(inv.begin(), inv.end(), index), inv.end());
}

Ответ 9

Более элегантным решением было бы перейти на std::list (при условии, что вам не нужен быстрый произвольный доступ).

list<Widget*> widgets ; // create and use this..

Затем вы можете удалить с помощью .remove_if и функтора С++ в одной строке:

widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;

Итак, здесь я просто пишу функцию, которая принимает один аргумент (Widget*). Возвращаемое значение является условием удаления Widget* из списка.

Я нахожу этот синтаксис приемлемым. Я не думаю, что когда-либо использовал remove_if для std::vectors - там было так много шума inv.begin() и inv.end(), что вы, вероятно, лучше используя удаление на основе целочисленного индекса или просто обычное старое стандартное удаление на основе итератора (как показано ниже). Но вы все равно не должны удаляться из середины std::vector, так что рекомендуется перейти на list для этого случая частого удаления списка из списка.

Заметьте, однако, что я не получил возможность вызвать delete на Widget*, которые были удалены. Для этого он будет выглядеть следующим образом:

widgets.remove_if( []( Widget*w ){
  bool exp = w->isExpired() ;
  if( exp )  delete w ;       // delete the widget if it was expired
  return exp ;                // remove from widgets list if it was expired
} ) ;

Вы также можете использовать обычный цикл, основанный на итераторе, следующим образом:

//                                                              NO INCREMENT v
for( list<Widget*>::iterator iter = widgets.begin() ; iter != widgets.end() ; )
{
  if( (*iter)->isExpired() )
  {
    delete( *iter ) ;
    iter = widgets.erase( iter ) ; // _advances_ iter, so this loop is not infinite
  }
  else
    ++iter ;
}

Если вам не нравится длина for( list<Widget*>::iterator iter = widgets.begin() ; ..., вы можете использовать

for( auto iter = widgets.begin() ; ...

Ответ 10

Я думаю, что я бы сделал следующее...

for (auto itr = inv.begin(); itr != inv.end();)
{
   // Do some stuff
   if (OK, I decided I need to remove this object from 'inv')
      itr = inv.erase(itr);
   else
      ++itr;
}

Ответ 11

Вы не можете удалить итератор во время итерации цикла, потому что количество итераторов не совпадает, и после некоторой итерации у вас будет недопустимый итератор.

Решение: 1) взять копию оригинального вектора 2) повторить итератор, используя эту копию 2) сделать что-нибудь и удалить его из оригинального вектора.

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

std::vector<IInventory*> copyinv = inv;
iteratorCout = 0;
for (IInventory* index : copyinv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    inv.erase(inv.begin() + iteratorCout);
    iteratorCout++;
}  

Ответ 12

Стирание элемента по одному легко приводит к производительности N ^ 2. Лучше пометить элементы, которые следует стереть, и стереть их сразу после цикла. Если я могу предположить, что nullptr является недопустимым элементом в вашем векторе, тогда

std::vector<IInventory*> inv;
// ... push some elements to inv
for (IInventory*& index : inv)
{
    // Do some stuff
    // OK, I decided I need to remove this object from 'inv'...
    {
      delete index;
      index =nullptr;
    }
}
inv.erase( std::remove( begin( inv ), end( inv ), nullptr ), end( inv ) ); 

должно работать.

Если ваше "Делать что-то" не изменяет элементы вектора и используется только для принятия решения об удалении или сохранении элемента, вы можете преобразовать его в лямбду (как это было предложено в предыдущем посте) и использовать

inv.erase( std::remove_if( begin( inv ), end( inv ), []( Inventory* i )
  {
    // DO some stuff
    return OK, I decided I need to remove this object from 'inv'...
  } ), end( inv ) );