【问题标题】:Iterating through a vector and erasing遍历向量并擦除
【发布时间】:2020-02-25 16:22:41
【问题描述】:

很明显,这段代码会产生段错误。

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<int> ints;
    ints.push_back(5);
    std::vector<int>::iterator it;
    for(it = ints.begin(); it != ints.end(); ++it){
        std::cout << *it;
        it = ints.erase(it);
    }
}

如果我们删除++it并添加一个擦除条件,我们可以避免这个错误。但问题的真正原因是什么?在我们的循环中,我们说,对于迭代器的开始,直到它到达结束,递增一,打印出值然后擦除它。是不是因为 ++it 在“结束”时被调用,而在这种情况下,我们已经删除了下一个(唯一的)值?

【问题讨论】:

  • 这对我一点帮助都没有。
  • 确实如此。未定义的行为意味着任何事情都可能发生,包括从你鼻子里飞出的恶魔。这正是该术语的起源,以强调这一点。可以肯定的是,您永远不会忘记这句话,并且您将永远铭记于心。
  • @errno_44 在最后一次迭代中ints.erase(it); 返回ints.end()。然后for 循环尝试++ 那个不允许的迭代器。
  • @SamVarshavchik 抱歉,这对我来说毫无意义。
  • 我从不喜欢擦除正在迭代的容器的元素..

标签: c++


【解决方案1】:

是不是因为在这种情况下,我们已经删除了下一个(唯一的)值,而在“结束”时调用了 ++it?

是的。

std::erase 将迭代器返回到被擦除元素之后的元素。

考虑一个只有一个元素的向量,那么你的循环基本上是:

it = ints.begin(); 
std::cout << *it;
it = ints.erase(it);    // it == ints.end()
++it;                   // boom
//if (it != ints.end()) // more boom

请看这里:Is it allowed to increment an end iterator? - 没有。

这些问题是首选擦除删除习惯而不是手写循环来擦除元素的原因之一。

【讨论】:

  • 谢谢。迭代器的目的不应该是为用户(即我)屏蔽工作。如果我在迭代器上 ++,但它已经结束了,为什么它不只是.. 不做任何事情?为什么 C 和 C++ 充满了陷阱!
  • @errno_44 因为保护开发人员免受陷阱有时是有代价的。在这种情况下,迭代器必须执行额外的检查。 C++ 有一个核心信念,即如果可以避免,它不应该强制开销。如果您想要这样的保护,您可以编写自己的迭代器并使用它。
  • @errno_44 -- 因为 C++ 不会做额外的工作来保证你的安全。 C++ 是一种你应该只为你使用的东西付费的语言。您决定不使用错误检查,这是您的事。
  • @errno_44 这意味着每个增量都必须进行额外的检查。而且,如果您考虑一下,那通常也无济于事,因为如果您的循环没有到达 end 而只到达 begin() + n 怎么办?在这种情况下,您将没有 UB,但您的代码仍然是错误的,并且检查 end 的增量将无济于事
  • @errno_44 请注意,在某些库实现中,迭代器提供针对大多数常见陷阱的保护,但仅限于调试版本。这些保护不会改变迭代器的行为。相反,它们会通过触发断点或以其他方式发出问题信号来警告您出现问题。
【解决方案2】:

你的假设是错误的。让我引用相关部分:

在我们的循环中,我们说,对于迭代器的开始,直到它到达结束递增一,打印出值然后擦除它。

该描述交换了两个粗体部分,这很重要。增量是在检查它是否到达末尾之前完成的。擦除最后一个元素时这是错误的。在这种情况下,您的迭代器已经在末尾,您仍然会在检查它是否到达末尾之前对其进行递增。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多