【问题标题】:Erasing vector elements while in a range-based loop vs. standard loop在基于范围的循环与标准循环中擦除矢量元素
【发布时间】:2020-07-17 07:47:09
【问题描述】:

如果我在标准 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 次,尽管向量的所有元素都被删除。

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'。

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

【问题讨论】:

    标签: c++ c++11 vector stdvector


    【解决方案1】:

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

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

    来自docs

    std::vector::erase

    ... 在该点处或之后使迭代器和引用无效 擦除,包括 end() 迭代器。

    【讨论】:

    • 感谢您的回答。在第一种情况下,一旦向量的大小等于 0,迭代结束对我来说似乎是合乎逻辑的,但我很难理解为什么在第二种情况下它没有结束。它是未定义的行为这一事实解释了很多。
    【解决方案2】:

    来自标准,[stmt.ranged]/1

    基于范围的 for 语句

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

    等价于

    {
      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。

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

    【讨论】:

    • 所以这是未定义的行为。非常感谢。
    • 为什么在名称中使用下划线? stackoverflow.com/questions/3136594/…
    • @northerner 这不是我,我是从cppreference 那里得到的,我认为它是在语言(或编译器)方面编写的。不管怎样,我把伪代码改成了从标准中得到的。
    猜你喜欢
    • 1970-01-01
    • 2015-09-28
    • 2018-07-21
    • 1970-01-01
    • 1970-01-01
    • 2015-04-26
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    相关资源
    最近更新 更多