如何使用for循环和反向迭代器调用erase函数

11

关于这里提供的答案:如何使用反向迭代器调用erase

下面的代码,当在g++ 4.8.4中使用 -std=c++11编译时,会导致段错误(在++it处)。我是否误解了答案?

  std::map<int,int> testmap;
  testmap[0] = 1;
  for(auto it=testmap.rbegin(); it!=testmap.rend(); ++it) {
    testmap.erase( std::next(it).base() );
  }

it = decltype(it){testmap.erase( std::next(it).base() )};替代++it - Jarod42
你不需要在反向迭代器中使用 --it 吗?你引用的答案似乎是这样建议的。 - Thinkeye
2
@Thinkeye 不是的:反向迭代器通过容器的反向视图向前遍历。答案将递减 it.base() -- 对应于反向迭代器的“正常”迭代器。 - Quentin
1
@logidelic的erase会使你传入的迭代器失效,并返回你应该继续遍历的迭代器。 - Quentin
2个回答

13

erase 会使迭代器失效,因此您必须从 erase 的返回值重新构造迭代器:

it = std::map<int,int>::reverse_iterator(testmap.erase( std::next(it).base() ));

或使用C++11:

it = decltype(it){testmap.erase( std::next(it).base() )};

或者使用C++17:

it = std::reverse_iterator(testmap.erase( std::next(it).base() ));

演示

为了完整起见,这是原始问题中已更正的循环的样子(请注意,迭代器增量已从for(...)中删除):

for (auto rit = testmap.rbegin(); rit != testmap.rend(); /* empty */) {
    if (WE_WANT_TO_ERASE(rit)) {
        rit = decltype(rit){ testmap.erase(std::next(rit).base()) };
    } else {
        ++rit;
    }
}

4
如果你使用erase函数,请记得跳过++it,否则可能会漏掉一个元素。 - Quentin
感谢您的回复...我相信您是正确的,但我很惊讶在其他的讨论中人们已经建议(没有挑战)这是不必要的。(例如,https://dev59.com/ZHI-5IYBdhLWcg3wfoW1#3808880) - logidelic
嗯,反向迭代器不是指向前一个元素或类似的东西吗?(因此,reverse(end())和reverse(begin())形成一个范围) - Yakk - Adam Nevraumont
@logidelic:在另一个问题中,有一个“break”来结束循环,这可能解释了为什么那个点没有被真正讨论。 - Jarod42
@Yakk:我不理解你的观点:{reverse_iterator(v.end()), reverse_iterator(v.begin())}是范围{v.rbegin(), v.rend()}。(实际上,它的内部应该指向一个有效的范围{v.begin(), v.end()} - Jarod42
@Jarod42 嗯。我猜它可以工作:erase指向被删除元素之后的下一个元素,而在反向迭代中,下一个元素之前的元素是下一个元素。 - Yakk - Adam Nevraumont

2

在使用这个习语一段时间后,我认为需要对Jarod42答案中的循环进行修改,以使事情更加安全,并保持典型的for(;;)循环的美好特性:

for (auto it = testcont.rbegin(), nit = it; it != testcont.rend(); it = nit) {
    nit = next(it);

    // whatever... maybe a continue somewhere or maybe not

    if (WE_WANT_TO_ERASE(it)) {
        nit = decltype(it){ testcont.erase(std::next(it).base()) };
    }

    // whatever... maybe a continue somewhere or maybe not
}

使用另一个答案中的循环太危险了。如果在循环中不经思考地添加了 continue;,而没有先增加迭代器,结果将是一个无限循环。由于乍一看,这可能看起来像一个普通的 for(;;) 循环,我相信这迟早会发生。同样,如果循环中有分支,并且其中一个分支忽略了增加迭代器,则会引入另一个错误。最后,如果你执行了一个 erase(),你需要确保在增加迭代器之前 continue,否则你又有另一个错误。
使用上面修改过的循环,循环可以像普通的 for(;;) 循环一样处理。诀窍是在循环体的第一行递增 nit("下一个迭代器")。然后你就不用担心了。唯一需要更新 nit 的时候是当你执行一个 erase()。其他所有操作都像正常的 for 循环一样工作。
最后要注意的一点:我最初提出这个问题是关于映射的,但这对于 vectorlist 等也适用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接