【问题标题】:Assertion failure: list iterator not incrementable (not in a loop)断言失败:列表迭代器不可递增(不在循环中)
【发布时间】:2017-11-11 07:50:52
【问题描述】:

首先,早上好/白天/晚上好,感谢所有花时间阅读本文的人。

设置:

在我的代码中,我有两个不同的类:ColObj 和 QuadNode(即,“碰撞对象”和四叉树中的一个节点,用于检查对象的接近度。我知道那里可能有用于此的库,但我需要编写自己的系统,所以它们在这里没有用)。事情是这样工作的:当一个 ColObj 对象被创建时,它被添加到一个适当的 QuadNode 中(该节点有一个指向 ColObj 的指针的 std::list),以便该节点可以在它与某物碰撞时通知它; ColObj 对象还接收指向持有它的节点的指针和指向包含其地址的列表迭代器的列表迭代器,因此当它超出节点的边界或被破坏时,它可以“离开”它并清理节点,即,从节点中删除并引用自身。我这样做是因为在很多情况下,这将是一个频繁的操作,我希望它在恒定的时间内。

守则:

这是用于将 ColObj “附加”到 QuadNode 的方法。我怀疑问题不在这里。

void QuadNode::obj_add(ColObj *obj) {

    std::cout<<"QuadNode at depth ("<<depth<<") received new ColObj.\n";

    objects.push_back(obj);

    obj->holder  = this;
    obj->my_iter = std::prev( objects.end() );

    if ((int)objects.size() > MAX_OBJECTS && depth < MAX_DEPTH) split();

    }

这是 ColObj 用来清理节点的 QuadNode 方法。这是由于某种原因出现问题的地方。

void QuadNode::obj_list_erase(std::list<ColObj*>::iterator iter) {

    std::list<ColObj*>::iterator iter2 = objects.begin();

    objects.erase(iter);

    }

这个方法的第一行只是为了提供额外的调试信息,后面会去掉。

错误:

最奇怪的是,在大多数情况下,代码运行良好。然后在某一时刻,它随机地抛出一个断言失败,说“列表迭代器是不可增加的”。这是第一个奇怪的事情,我不想在我的代码中的任何地方增加它(尽管我知道 std::list::erase 返回以下迭代器,但我从未尝试对无效或“过去的-最后一个”迭代器)。

无论如何,Visual Studio 提供启动调试器并在代码中设置断点,所以我自然同意。所以这是最奇怪的部分:

Local and auto variables, debugger screenshot

(由于我是新来的,所以无法嵌入图像,所以它就是这样)。

所以,除非我在这里严重错误,否则它告诉我传递的迭代器等于列表的开始迭代器,它的元素仍然存在于列表中并且对应于第一个(或者更确切地说是零) 列表的元素。然而,erase() 方法失败了。 对于它的价值,我注意到每次程序中断时,传递的迭代器都指向列表的第零个元素,尽管我可以确认即使列表中只有一个元素,该方法通常也有效。

其他信息和结论:

我不会在代码中的其他任何地方手动递增迭代器(无论如何它非常小且简单)。

我使用的 IDE 是 Visual Studio Community 2015,但我不知道编译器版本。 (微软及其命名方案...)

我尝试在 SO 上找到另一个关于此的线程,但我检查的每个线程都是关于错误地将 i++ 放置在列表迭代中,如果这是重复线程,请见谅。

我完全被这个问题弄糊涂了,因为通常在优秀的调试器、std::cout 和浏览器之间,所以我以某种方式解决了这个问题,但是这次没有任何有用的东西出现,所以任何建议或建议都会非常欢迎。

编辑:

我尝试过“只是因为”编辑 QuadNode::obj_list_erase 方法的一件事,以便它将传递的迭代器与其列表的第一个迭代器 (objects.begin()) 进行比较,如果它们是相等的使用对象。 pop() 删除它,否则正常删除它。它不起作用,说迭代器不兼容,不管这意味着什么......

【问题讨论】:

  • 这是一个极好的“建议或建议”:TL;DR。将您的代码减少到minimal reproducible example,任何人都可以使用它来重现您的问题,然后用您的minimal reproducible example 替换您在上面编写的所有内容。调试器所做的只是向您显示程序崩溃时的状态。程序崩溃时的状态完全无关紧要。你需要知道为什么它会达到那个状态。除非很明显,否则从事后分析来看,推测都是浪费时间。
  • “我不知道编译器版本。(Microsoft 及其命名方案...)” - 打开开发人员命令提示符并键入 cl.exe /?。这将显示编译器版本,无论 Microsoft 选择使用何种命名方案。 (无论如何它有什么问题?)此外,断言对话框不提供设置断点。它提供了带你到失败的调试断言的权利。去做吧。调用堆栈将显示出来。
  • @IInspectable 好的,所以我检查了编译器版本,它是:19.00.24215.1,优化 x86 编译器(谢谢你的提示,我不知道你可以这样做。)。我在断言之后再次检查了调用堆栈(我之前已经检查过),但它只显示了预期的调用链,然后是[外部代码],我猜这不是你的目标。无论如何,我会按照 Sam Varshavchick 的建议做一个最小的例子。
  • 1) 在 QuadNode::obj_list_erase 中,您从列表中删除指针,但不删除对象(可能存在内存泄漏); 2)这是我的猜测(基于您提供的 sn-p): split() 拆分列表,因此使存储在 ColObj 对象中的迭代器无效,然后您从 QuadNode::obj_list_erase 中的 ColObj 对象之一传递无效的迭代器和奇怪的事情发生了(ub)
  • QuadNode 不拥有 ColObj 对象,因此删除它们不是它的责任,只需“通知它们”直到从所述节点中删除对它们的引用。并且 split() 在某些时候调用 obj_add() 进而更新对象的持有者信息。

标签: c++ list iterator erase assertion


【解决方案1】:

在发现我什至无法将传递的迭代器与应该持有它的列表中的任何其他迭代器进行比较(我遇到断言失败:迭代器不兼容)后,我搜索了 SO 以获取有关其含义的更多信息,而且……安德鲁·卡什普尔是对的。我确实设法通过从列表中删除指向的元素并立即将其放回去来使迭代器无效,但没有更新迭代器。

故事的寓意:迭代器似乎指向一个“正确”的内存位置,它甚至可能指向与某些有效迭代器相同的地址,但这并不使其有效或兼容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-16
    • 1970-01-01
    • 2011-09-04
    • 1970-01-01
    • 2013-06-10
    • 2016-05-18
    相关资源
    最近更新 更多