从矢量中删除项目,而在 C++11 范围内'for'循环?

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

本文关键字:for 循环 范围内 C++11 删除项目 而在      更新时间:2023-10-16

我有一个 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'...
}

不,你不能。基于范围的for适用于需要访问容器的每个元素一次的情况。

如果需要在进行过程中修改容器、多次访问元素或以其他方式以非线性方式遍历容器,则应使用正常的 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()测试。如果您尝试按如下方式优化它:

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

很抱歉死灵,也很抱歉我的 C++ 专业知识妨碍了我的回答,但如果您尝试遍历每个项目并进行可能的更改(例如擦除索引(,请尝试使用 backwords for loop。

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

与这个线程标题相反,我会使用两个传递:

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

一个更优雅的解决方案是切换到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()噪音,你可能最好使用基于整数索引的删除或只是一个普通的基于常规迭代器的删除(如下所示(。 但是无论如何,您都不应该真正从std::vector中间删除,因此建议在这种频繁删除列表中间的情况下切换到list

请注意,但是我没有机会在已删除的Widget*上致电delete。 为此,它看起来像这样:

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

我会做以下事情...

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(复制原始矢量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 性能。最好标记应该擦除的元素,并在循环后立即擦除它们。如果我可以在您的向量中假定无效元素中的 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 ) ); 

应该工作。

如果你的"做一些事情"没有改变向量的元素,并且仅用于决定删除或保留元素,你可以将其转换为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 ) );