Удаление элемента из вектора, в то время как в цикле 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 ответов:
нет, вы не можете. На основе диапазонов
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
anerase
делает недействительными все итераторы, начиная с элемент стирается до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*
). Возвращаемое значение-это условие, при котором необходимо удалить aWidget*
из списка.я нахожу этот синтаксис приемлемым. Я не думаю, что когда-нибудь буду использовать
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() ; ...