在C ++ 11范围for for循环中移除vector中的项目?
我有一个IInventory *的向量,我正在循环使用C ++ 11范围的列表,每个都做一些东西。
在做了一些东西之后,我可能想从列表中删除它并删除对象。 我知道我可以在指针上随时调用delete
来清理它,但是在循环的范围内for
从vector中移除它的正确方法是什么? 如果我从列表中删除它,我的循环将被视为无效?
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'... }
不,你不能。 基于范围的是用于需要访问容器的每个元素一次。
如果您需要随时修改容器,不止一次访问某个元素,或者以非线性方式在容器中迭代,则应该使用正常的for
循环或其一个表亲。
例如:
auto i = std::begin(inv); while (i != std::end(inv)) { // Do some stuff if (blah) i = inv.erase(i); else ++i; }
每当元素从向量中移除时,都必须假设在被擦除元素处或之后的迭代器不再有效,因为擦除元素之后的每个元素都将被移动。
基于范围的for循环仅仅是使用迭代器的“正常”循环的语法糖,所以上述情况适用。
这就是说,你可以简单地:
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
一个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()
testing。 如果您尝试如下优化它:
for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)
你会遇到UB,因为你的e
在第一次erase
之后无效。
在这个循环中删除元素是否是一个严格的要求? 否则,您可以将要删除的指针设置为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);
一个更优雅的解决scheme是切换到std::list
(假设你不需要快速的随机访问)。
list<Widget*> widgets ; // create and use this..
然后你可以在一行中用.remove_if
和一个C ++函子删除:
widgets.remove_if( []( Widget*w ){ return w->isExpired() ; } ) ;
所以在这里我只是写一个函数接受一个参数( Widget*
)。 返回值是从列表中删除Widget*
的条件。
我觉得这个语法可口。 我不认为我会永远使用remove_if
为std ::向量 – 那里有这么多的inv.begin()
和inv.end()
噪声你可能更好使用基于整数索引的删除或只是一个普通的旧的常规基于迭代器的删除(如下所示)。 但是,你不应该真正从std::vector
的中间去掉,所以build议把这个频繁的列表删除的情况切换到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() ; ...
对于necroposting抱歉,也抱歉,如果我的c ++专业知识阻碍我的答案,但如果你试图遍历每个项目,并做出可能的更改(如擦除索引),尝试使用backwords for循环。
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实现至less)做一些非常相似的事情(使用经典的循环…),只是不删除任何东西,不抹去。
但请注意,这不是线程安全的(!) – 但是,这也适用于上面的其他解决scheme…