关于c ++:从vector中删除项目,而在C ++ 11范围内’for’循环?

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

我有一个iToSt*的向量,并且我用C++ 11范围来循环列表,来处理每一个。

在对其中一个做了一些事情之后,我可能想从列表中删除它并删除该对象。我知道我可以随时调用指针上的delete来清理它,但是在for循环范围内,从向量中删除它的正确方法是什么?如果我把它从列表中删除,我的循环会失效吗?

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'...
}


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

如果您需要在执行过程中修改容器、多次访问元素或以非线性方式通过容器迭代,则应使用普通的for循环或其一个同类循环。

例如:

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


理想情况下,在迭代向量时不应该修改它。使用"删除"习语。如果你这样做了,你可能会遇到一些问题。由于在vector中,erase使从元素被擦除到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;
    }
}

请注意,您需要按原样进行b != v.end()测试。如果您尝试如下优化它:

1
for (MyVector::iterator b = v.begin(), e = v.end(); b != e;)

你会遇到ub,因为你的e在第一次erase呼叫后失效。


在那个循环中删除元素是一个严格的要求吗?否则,可以将要删除的指针设置为空,并再次传递向量以删除所有空指针。

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

记住,方法erase将返回所传递迭代器的下一个迭代器。

从这里,我们可以使用更为生成的方法:

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

请参阅此处了解如何使用std::remove_if。https://en.cppreference.com/w/cpp/algorithm/remove/删除


抱歉,如果我的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;
}

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

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

然后,可以用EDCOX1、9和一个C++函子删除一行:

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

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

我觉得这个语法很好用。我不认为我会将remove_if用于std::vectors——这里有太多inv.begin()inv.end()噪声,您最好使用基于整数索引的删除或简单的基于正则迭代器的删除(如下所示)。但无论如何,你不应该真的从std::vector的中间移走,因此建议切换到list,因为这种情况下经常删除中间的列表。

但是请注意,我没有机会在删除的Widget*上打电话给delete。要做到这一点,应该是这样的:

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

如果您不喜欢for( list::iterator iter = widgets.begin() ; ...的长度,可以使用

1
for( auto iter = widgets.begin() ; ...