我有一个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'...
}
答案 0 :(得分:76)
for
适用于需要访问容器的每个元素的时间。
如果您需要随意修改容器,多次访问元素或以非线性方式迭代容器,则应使用普通for
循环或其中一个表兄弟
例如:
auto i = std::begin(inv);
while (i != std::end(inv)) {
// Do some stuff
if (blah)
i = inv.erase(i);
else
++i;
}
答案 1 :(得分:49)
每次从向量中移除一个元素时,必须假设在擦除元素之后或之后的迭代器不再有效,因为移动了擦除元素之后的每个元素。
基于范围的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()
);
答案 2 :(得分:11)
理想情况下,迭代时不应修改向量。使用擦除删除习语。如果你这样做,你可能会遇到一些问题。由于在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()
测试。如果您尝试按如下方式对其进行优化:
for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)
由于e
在第一次erase
来电后{{1}}失效,您将遇到UB。
答案 3 :(得分:6)
在该循环中删除元素是否严格要求?否则,您可以将要删除的指针设置为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);
答案 4 :(得分:1)
好的,我迟到了,但无论如何:对不起,不正确我到目前为止读到的内容 - 可能,你只需要两个迭代器:
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实现)做了非常相似的事情(使用经典循环...),只是不删除任何内容而不删除。
但请注意,这不是线程安全的(!) - 但是,这也适用于上述其他一些解决方案......
答案 5 :(得分:1)
对于necroposting很抱歉,如果我的c ++专业知识妨碍了我的答案,也很抱歉,但是如果你试图遍历每个项目并进行可能的更改(比如删除索引),请尝试使用后缀循环。
for(int x=vector.getsize(); x>0; x--){
//do stuff
//erase index x
}
当擦除索引x时,下一个循环将用于最后一次迭代的“前面”项。我真的希望这有助于某人
答案 6 :(得分:1)
我将通过示例显示,下面的示例从向量中删除奇数元素:
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
将返回传递的迭代器的下一个迭代器。
在here 中,我们可以使用更多的generate方法:
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
答案 7 :(得分:0)
更优雅的解决方案是切换到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::vectors - 有很多inv.begin()
和inv.end()
噪音,你可能最好使用an integer-index-based delete或者只是一个普通的旧的常规迭代器删除(如下所示)。但是你不应该真的从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() ; ...
答案 8 :(得分:0)
我想我会做以下事情......
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;
}
答案 9 :(得分:0)
与该主题相反,我将使用两遍:
#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());
}
答案 10 :(得分:0)
您不能在循环迭代期间删除迭代器,因为迭代器计数不匹配,并且在某些迭代之后您将拥有无效的迭代器。
解决方案: 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++;
}
答案 11 :(得分:0)
一个接一个的擦除元素很容易导致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 ) );
应该工作。
如果您的“做一些事情”没有改变vector的元素,而只是用于决定删除或保留该元素,则可以将其转换为lambda(如某人先前的帖子中所建议)并使用>
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 ) );