Removing item from vector, while in C++11 range 'for' loop?

I have a vector of IInventory*, and I am looping through the list using C++11 range for, to do stuff with each one.

After doing some stuff with one, I may want to remove it from the list and delete the object. I know I can call delete on the pointer any time to clean it up, but what is the proper way to remove it from the vector, while in the range for loop? And if I remove it from the list will my loop be invalidated?

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'...
}
94343 次浏览

不,你不能。基于范围的 for是当你需要访问一个容器的每个元素一次。

如果您需要随时修改容器,多次访问一个元素,或者以非线性方式在容器中迭代,那么应该使用常规的 for循环或它的一个近亲。

For example:

auto i = std::begin(inv);


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

在理想情况下,迭代时不应该修改向量。使用擦除-删除成语。如果您这样做,您可能会遇到一些问题。因为在 vector中,从元素被擦除到 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;)

you will run into UB since your e is invalidated after the first erase call.

每次从向量中删除一个元素时,您必须假定擦除元素之前或之后的迭代器不再有效,因为继承擦除元素的每个元素都被移动了。

基于范围的 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()
);

在那个循环中删除元素是一个严格的要求吗?否则,您可以将要删除的指针设置为 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);

一个更优雅的解决方案是切换到 std::list(假设您不需要快速随机访问)。

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

然后您可以使用 .remove_if和 C + + 函数在一行中删除:

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

这里我只写了一个函数,它只接受一个参数(Widget*)。返回值是从列表中删除 Widget*的条件。

我觉得这种语法很美味。我不认为我会使用 remove_if向量-有这么多的 inv.begin()inv.end()噪声,你可能更好地使用 基于整数索引的删除或只是一个普通的旧的基于迭代器的删除(如下所示)。但是你不应该真的从一个 std::vector的中间删除很多无论如何,所以切换到一个 list为这种情况下频繁的中间列表删除建议。

注意,然而,我没有得到一个机会调用 deleteWidget*的被删除。要做到这一点,它看起来像这样:

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() ; ...

好的,我迟到了,但是无论如何: 对不起,我没有更正我读到的内容——它可能是 ,你只需要两个迭代器:

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 实现)做了一些非常类似的事情(使用一个经典的循环...) ,只是不删除任何东西,也不擦除。

Be aware, however, that this is not thread safe(!) - however, this applies, too, for some of the other solutions above...

很抱歉,如果我的 c + + 专业知识妨碍了我的回答,也很抱歉,但是如果你试图遍历每个条目并做出可能的修改(比如擦除索引) ,尝试使用一个反词作为循环。

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


//do stuff
//erase index x


}

当擦除索引 x 时,下一个循环将用于最后一次迭代“前面”的项。我真心希望这对谁有帮助

我想我会做下面这些..。

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

我将用例子来说明,下面的例子去除矢量中的奇数元素:

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;


}

产出如下:

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_ifHttps://en.cppreference.com/w/cpp/algorithm/remove

In opposition to this threads title, I'd use two passes:

#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());
}

在循环迭代期间不能删除迭代器,因为迭代器计数不匹配,而且在一些迭代之后,迭代器将是无效的。

解决方案: 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++;
}

逐个删除元素容易导致 N ^ 2性能。 最好标记应该被擦除的元素,并在循环之后立即擦除它们。 If I may presume nullptr in not valid element in your vector, then

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

应该可以。

如果你的“ Do some stuff”没有改变向量的元素,而只是用来决定删除或保留元素,那么你可以把它转换成 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 ) );