【问题标题】:STL iterator before std::map::begin()std::map::begin() 之前的 STL 迭代器
【发布时间】:2013-10-25 09:06:20
【问题描述】:

在 C++11 的 std::map 中,是否有一些有效的迭代器 x 保证 ++x 等于 map::begin()?我想检测我刚刚调用的函数(我的)是否将迭代器从函数的前面移开。该函数会将迭代器向后移动一个位置。

答案是否适用于图书馆的其余部分?

【问题讨论】:

  • 简短回答:不。您确实需要找到其他方法来处理(或者更好的是,防止)这种情况。
  • @JerryCoffin 这就是我们有反向迭代器的原因,看看我的回答

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


【解决方案1】:

通过“将迭代器从前面移开”,我认为您正在减少前向迭代器,如下所示:

// don't do this:
for(it = mymap.end(); --it >= mymap.begin(); ) { ... }

相反,像这样递增一个反向迭代器:

// this is better:
for(it = mymap.rbegin(); it != mymap.rend(); ++it) { ... }

-杰西

【讨论】:

  • 如果我使用反向迭代器,我在使用另一个函数时也会遇到同样的问题,但 map 的末尾并移动迭代器向前
  • 出于好奇,您为什么需要将迭代器移动到与其自然方向相反的方向? do { ... } while (it != mymap.begin();
  • 我正在实现另一个迭代器,它必须围绕我正在编写的地图树进行迭代。 ForwardIterator 工作正常;现在我要去BidirectionalIterator
  • 我怀疑你是对的,begin()-1 是未定义的。如果您已经在 end() 处,您可能会在递增之后但在操作之前进行检查,如果您刚刚处理 begin(),则在操作之后但在递减之前进行检查。
  • @thirtythreeforty 在向前移动时使用常规迭代器,在向后移动时使用反向迭代器。或者,如果您想使用常规迭代器进行反向迭代,请确保永远不要减少 begin(),因为这需要 UB。请参阅我的回答,了解 4 种迭代方式。
【解决方案2】:

不,std 容器中开头之前的迭代器都是 UB(反向迭代器除外,它可能无法解决您的问题)。

您可能需要修复有问题的功能。如果做不到这一点,请在调用它之前将其包装并捕获不良行为。否则,您可以在map 键类型排序中插入一个负无穷大元素,并添加一个标记值。如果做不到这一点,您可以编写迭代器适配器,将您的 map 迭代器与可以在没有 UB 的情况下一开始就进行的迭代器相包装。

这些大致按照我的推荐顺序排列。每个都有可能失败的方法,并且随着我的建议变得越来越遥远,它们变得更容易出错和危险。

【讨论】:

  • 迭代器包装器似乎乍一看很干净,然后我想我将如何使用它们,它变得非常讨厌,很快。
  • @thirtythreeforty 是的,这就是我包含它的原因,但只是作为一个远程“哦,我的上帝,没有其他工作”选项。借助 boost 迭代器 fascade 的帮助,它只会有点讨厌。或者手写。或者延迟连接两个提升类型擦除的迭代器范围。 (再次,按推荐顺序)。如果你拿最后一个中的最后一个,你会得到你应得的:它在理论上有效,但是闻起来很臭。简而言之,只需修复函数,它不应该递减没有有效范围的迭代器。
  • 咳咳,std::forward_list 确实有 before_begin() 成员
  • @TemplateRex:这是标准库中的一个容器,您不能“将迭代器从 front 中移出”。我不认为这是巧合。
  • @MSalters 当然,但关键是最好通过检查 rend() 来避免走在前面,而不是通过 UB 递减 begin() 并做 Wyle E. Coyote,看看我的回答下面
【解决方案3】:

意识到标准库容器是半开放范围[begin, end) 是非常重要的,即您可以迭代一次过去。对于双向(和随机)迭代器,您也可以执行--end() 并从边缘回来。通过*end() 取消对one-past-the-end 的引用是未定义的行为,因此通过--begin()begin() - 1 将开始迭代器递减。只有一个例外:std::forward_list 有一个满足 ++before_begin() == begin() 的不可取消引用的迭代器 before_begin()(但请注意,对于 forward_list,您也不能减少 begin())。

双向迭代器的这种基本不对称意味着反向迭代器是常规迭代器的薄包装。在大多数标准库实现中,它们只包含一个底层迭代器的副本base_。增加std::reverse_iterator 调用类似--base_; return *this; 的东西,并取消引用 它确实auto old = base_; return *--old;。在任何时候,底层迭代器都不会递减到 begin() 之前,并且不会以这种方式取消对 end() 的引用。

以下是对支持双向或随机迭代器的容器进行迭代的四种方法,以及各种迭代器之间的关系(.base()std::reverse_iterator 转换回其底层迭代器)

#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <string>

int main()
{    
    auto c = std::map<int, std::string>{ {1, "hello"}, {2, "world"} };
    
    {   // 1) forward iteratation
        auto it = begin(c);
        for (; it != end(c); ++it){}
        std::cout << std::boolalpha << (it == c.rbegin().base()) << "\n";
    }

    {   // 2) meh, backward iteration
        auto it = end(c) - 1; //end return iterator after the last element.
        for (; it != begin(c); --it){}
        std::cout << std::boolalpha << (it == c.rend().base()) << "\n";
    }

    {   // 2') better: reverse iteration
        auto it = c.rbegin();
        for (; it != c.rend(); ++it){}
        std::cout << std::boolalpha << (it.base() == begin(c)) << "\n";
    }

    {   // 1') backward reverse, better avoid this
        auto it = c.rend();
        for (; it != c.rbegin(); --it){}
        std::cout << std::boolalpha << (it.base() == end(c)) << "\n";
    }
}

Live Example

如果你有应该支持双向迭代的数据结构,但没有成员迭代器.rbegin()rend(),你可以分别通过std::reverse_iterator(end())std::reverse_iterator(begin()) 轻松定义它们(这也是标准库通常会实现它们)。

【讨论】:

  • 所以感谢您对我的回答中的废话投反对票,我只想说我在回答中说它是 UB 并且 UB 不是魔鬼,如果你只需要拥有你的代码在 one 位置编译并记录它是 UB 有什么问题。话虽如此,他应该能够使用反向迭代器,但我只是在回答 他的问题
  • @aaronman 很遗憾听到您对否决票感到不安。公平地说,我是 3 个不投票者中唯一一个解释我这样做的原因的人。请不要个人认为,我并没有说你的答案是废话,但 SO 答案也应该对未来的读者有用。 UB 真的是魔鬼,因为它可以悄悄地破坏你的代码。
  • 我个人会在我编写的任何代码中避免使用 UB,但如果有人明确要求不要以正确的方式(使用反向迭代器)做事,我会给他一个答案,提到答案是 UB ID看看有什么大不了的。也尊重实际评论我的答案,所以我可以像其他 DV 那样责备你:)
  • @aaronman UB “魔鬼”(如果你坚持措辞的话)。您(正确地)认为有时可以的是“实施定义的行为”(它仍然未指定,但并非未定义!)。因为这是您的供应商做出的承诺。一种不承诺执行您编写的代码的语言是一种我永远不会再用于任何事情的语言。
  • @aaronman:UB总是要避免。我承认,实现定义和未指定的行为更少。但是 UB 真的很糟糕——它处于机器级别的不确定性,你不能要求你的供应商或编译器保证任何结果,即使在你的程序连续运行相同的输入时也是如此。
猜你喜欢
  • 1970-01-01
  • 2012-09-13
  • 2015-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-30
  • 2020-09-13
  • 1970-01-01
相关资源
最近更新 更多