【问题标题】:C++11 reverse iterator with while loop带有while循环的C ++ 11反向迭代器
【发布时间】:2019-02-12 14:56:03
【问题描述】:

我知道代码不是好习惯,所以问题不在于那个。我只想了解以下示例的工作原理。请注意,当我调用 remove 时,我不对迭代器做任何事情,所以当循环进入下一次迭代时,它是如何指向下一个元素的?

#include <string>
#include <list>
#include <algorithm>
#include <iostream>

class Obj;
std::list<Obj> objs;

class Obj
{
public:
  Obj(const std::string& name, int age)
  : name_(name), age_(age)
  {}

  std::string name()
  {
    return name_;
  }

  int age()
  {
    return age_;
  }
private:
  std::string name_;
  int age_;
};


void remove(const std::string& name)
{
  auto it = find_if(objs.begin(), objs.end(),[name] (Obj& o) { return (o.name() == name); });
  if (it != objs.end())
  {
    std::cout << "removing " << it->name() << std::endl;
    objs.erase(it);
  }
}

int main()
{
  objs.emplace_back("bob", 31);
  objs.emplace_back("alice", 30);
  objs.emplace_back("kevin", 25);
  objs.emplace_back("tom", 45);
  objs.emplace_back("bart", 37);
  objs.emplace_back("koen", 48);
  objs.emplace_back("jef", 23);
  objs.emplace_back("sara", 22);

  auto it = objs.rbegin();
  while (it != objs.rend())
  {

   std::cout << it->name() << std::endl;

   if (it->name() == "tom")
   {
      remove(it->name()); //notice I don't do anything to change the iterator
   }
   else
   {
     ++it;
   }
  }
  return 0;
}

以下是输出:

sara
jef
koen
bart
tom
removing tom
kevin
alice
bob

【问题讨论】:

  • 您的代码具有未定义的行为,因为您更改了使所有迭代器无效的列表。也请不要使用全局变量。
  • @Swordfish "你改变了使所有迭代器无效的列表" - 不,它没有; "References and iterators to the erased elements are invalidated. Other references and iterators are not affected.".
  • @DanielLangr 确实如此并且变得无效。很抱歉我之前评论的措辞有误,并非*所有*迭代器都无效。
  • @Swordfish 是的,只删除了一个,虽然在下一步中会引用它,所以就足够了。
  • @luk32 “这就够了”这应该是什么意思?在remove(it-&gt;name()); 之后通过it-&gt;name() 访问它是未定义的行为。严格来说,it != objs.rend() 也是。

标签: c++ list c++11 iterator reverse-iterator


【解决方案1】:

我的另一个答案是不正确的。观察到的行为归因于reverse_iterator 的实现。来自cppreference:

std::reverse_iterator 是一个迭代器适配器,它反转给定迭代器的方向。换句话说,当提供双向迭代器时,std::reverse_iterator 会生成一个新迭代器,该迭代器会从底层双向迭代器定义的序列的末尾移动到开头。

对于从迭代器i 构造的反向迭代器r,关系&amp;*r == &amp;*(i-1) 始终为真(只要r 是可解引用的);因此,从一个过去的迭代器构造的反向迭代器取消对序列中最后一个元素的引用。

(强调我的)。另见[reverse.iterator]。

好的,这对我们意味着什么:当一个反向迭代器it 指向“tom”时,它实际上环绕了一个指向下一个元素“bart”的正向迭代器。当你取消引用它时,它会在包装的迭代器之前有一个元素,即“bart”之前的一个元素,它确实是“tom”。

当您删除“tom”时,包装的迭代器不会改变。 (也没有失效。)它仍然指向“bart”。当您取消引用反向迭代器时,它会查找“bart”之前的内容,现在是“kevin”。

这意味着您不会真正导致未定义的行为。如果您在第 60 行调用 remove("bart"),您会这样做。

【讨论】:

    【解决方案2】:

    您使迭代器无效,方法是删除它所寻址的对象(无论您是否使用它的值用于该目的)。如果您在那之后尝试访问它,则行为未定义(阅读:任何事情都可能发生,例如相同的it 跳转到下一个元素,或者您的程序崩溃)。您不能将其依赖于任何其他行为。

    【讨论】:

    • 好的,我知道它已经失效了。您能否详细说明“就像它跳到下一个元素一样”。如果失效了怎么办?我已经对此进行了超过 100000 次的测试,并且下一次迭代总是只打印“kevin”,这是下一个元素。
    • 这只是对您所说(或观察到)发生的事情的改写。这不是我想出的例子。
    • 我对无效迭代器的行为了解不多。如果它无效,它不应该类似于在 nullptr 上调用函数还是这是一个错误的假设?
    • 调用无效的迭代器是未定义的行为。在您的情况下,您的标准库的实现恰好使迭代器指向正确的项目,因此可以正常工作,但您不能依赖它。
    • 不要做出任何假设,@rinn。没有确定性的行为。这完全取决于容器和迭代器是如何实现的,或者你的记忆中有什么,或者你所在城市的官方限制范围内有多少只猫。只需按规范编写代码,不用担心。
    猜你喜欢
    • 1970-01-01
    • 2019-11-20
    • 2015-06-25
    • 2016-06-03
    • 1970-01-01
    • 2011-07-24
    • 1970-01-01
    • 2019-07-22
    • 1970-01-01
    相关资源
    最近更新 更多