Удаление элемента из вектора, в то время как в цикле C++11 range 'for'?


у меня есть вектор IInventory*, и я перебираю список, используя диапазон C++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'...
}
9 79

9 ответов:

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

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

например:

auto i = std::begin(inv);

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

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

диапазон на основе 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()
);

вы в идеале не должны изменять вектор во время итерации по нему. Используйте идиому стереть-удалить. Если вы это сделаете, вы, вероятно, столкнетесь с несколькими проблемами. Поскольку в 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;
    }
}

обратите внимание, что вам понадобится

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

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

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

//do stuff
//erase index x

}

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

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

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 по крайней мере) делает что-то очень похожее (используя классический цикл...), просто ничего не удаляет и не стирает.

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

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

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

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

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

затем вы можете удалить с .remove_if и функтор C++ в одной строке:

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

так что здесь я просто пишу функтор, который принимает один аргумент (Widget*). Возвращаемое значение-это условие, при котором необходимо удалить a 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() ; ...

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

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