Removing item from vector, while in C++11 range 'for' loop?
我有一个iToSt*的向量,并且我用C++ 11范围来循环列表,来处理每一个。
在对其中一个做了一些事情之后,我可能想从列表中删除它并删除该对象。我知道我可以随时调用指针上的
1 2 3 4 5 6 7 8 9 | 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 2 3 4 5 6 7 8 9 | auto i = std::begin(inv); while (i != std::end(inv)) { // Do some stuff if (blah) i = inv.erase(i); else ++i; } |
每次从向量中删除一个元素时,必须假定在被删除元素处或之后的迭代器不再有效,因为在被删除元素之后的每个元素都会被移动。
基于范围的for循环只是使用迭代器对"normal"循环进行语法调整,因此上面的内容适用。
也就是说,你可以简单地:
1 2 3 4 5 6 7 8 9 10 11 | 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() ); |
理想情况下,在迭代向量时不应该修改它。使用"删除"习语。如果你这样做了,你可能会遇到一些问题。由于在
1 2 3 4 5 6 7 | 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; } } |
请注意,您需要按原样进行
1 | for (MyVector::iterator b = v.begin(), e = v.end(); b != e;) |
你会遇到ub,因为你的
在那个循环中删除元素是一个严格的要求吗?否则,可以将要删除的指针设置为空,并再次传递向量以删除所有空指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); |
我将举例说明,下面的例子从向量中删除奇数元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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:
1 2 3 | 024 024 024 |
记住,方法
从这里,我们可以使用更为生成的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } |
请参阅此处了解如何使用
抱歉,如果我的C++专业知识妨碍了我的答案,我也很抱歉,但是如果你试图遍历每一个项目并做出可能的改变(比如擦除一个索引),试着使用一个for循环。
1 2 3 4 5 6 | for(int x=vector.getsize(); x>0; x--){ //do stuff //erase index x } |
删除索引x时,下一个循环将针对上一个迭代前面的项。我真的希望这能帮助别人
好吧,我迟到了,但不管怎样:抱歉,我到目前为止还没有纠正我读到的内容-有可能,您只需要两个迭代器:
1 2 3 4 5 6 7 8 9 10 11 12 13 | std::vector<IInventory*>::iterator current = inv.begin(); for (IInventory* index : inv) { if(/* ... */) { delete index; } else { *current++ = index; } } inv.erase(current, inv.end()); |
仅仅修改迭代器指向的值不会使任何其他迭代器失效,因此我们可以在不必担心的情况下完成此操作。实际上,EDOCX1(至少是gcc实现)做了一些非常类似的事情(使用一个经典的循环…),只是不删除任何内容,也不删除任何内容。
但是,请注意,这不是线程安全的!!)-然而,这也适用于上面的一些其他解决方案…
与这个主题相反,我将使用两个通行证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include #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()); } |
我想我会做以下的事情…
1 2 3 4 5 6 7 8 | 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; } |
一个更优雅的解决方案是切换到
1 | list<Widget*> widgets ; // create and use this.. |
然后,可以用EDCOX1、9和一个C++函子删除一行:
1 | widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ; |
所以这里我只写了一个接受一个论点的函数(
我觉得这个语法很好用。我不认为我会将
但是请注意,我没有机会在删除的
1 2 3 4 5 | 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 } ) ; |
也可以使用基于正则迭代器的循环,如下所示:
1 2 3 4 5 6 7 8 9 10 11 | // 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 ; } |
如果您不喜欢
1 | for( auto iter = widgets.begin() ; ... |