在基于范围的循环与标准循环中擦除矢量元素

Erasing vector elements while in a range-based loop vs. standard loop

如果我在标准 for 循环的第一次迭代中删除向量的所有 5 个元素

std::vector<int> test {1, 2, 3, 4, 5};

for(int i = 0; i < test.size(); i++)
{
    if(test[i] == 1) test.erase(test.begin(), test.end());
    std::cout << i << " ";
}

它只会迭代一次,std::cout 输出将为“0”。

但是,如果我使用基于范围的循环做同样的事情,它将迭代 5 次,尽管 vector 的所有元素都被删除了。

int i = 0;
for (auto &a: test)
{
    if (a==1) test.erase(test.begin(), test.end());
    std::cout << i << " ";
    i++;
}

并且 std::cout 输出将为“0 1 2 3 4”。

使用这两种类型的循环时,这种不同的行为从何而来?

第一个情况下,每次迭代都会调用std::vector::size函数。因此,如果您在第一次迭代中删除所有元素,则在第二次迭代开始之前调用的 std::vector::size 函数将 return 0。因此,第二次迭代不会发生,因为条件 i < test.size()不满意

second情况下,基于范围的for循环使用迭代器而不是std::vector::size函数。当您调用 std::vector::erase 时,您会使所有迭代器失效,包括 end() 迭代器。因此,第二种情况实际上是 UB(未定义行为),你应该永远不要依赖它。

来自docs

std::vector::erase

... Invalidates iterators and references at or after the point of the erase, including the end() iterator.

根据标准,[stmt.ranged]/1

The range-based for statement

for ( init-statement opt for-range-declaration : for-range-initializer ) statement

is equivalent to

{
  init-statement opt     
  auto &&range = for-range-initializer ;
  auto begin = begin-expr ;
  auto end = end-expr ;
  for ( ; begin != end; ++begin ) {
      for-range-declaration = * begin ;
      statement
  }
}

请注意,开始和结束迭代器是在循环开始之前初始化的。如果您在使迭代器无效的循环中执行擦除,则代码会导致 UB。

另一方面,第一个代码片段每次迭代都会检查 test.size()。当元素被擦除并且 size() 变为 0 时,循环立即结束。