【问题标题】:How to handle multiple iterator types如何处理多个迭代器类型
【发布时间】:2011-12-16 14:45:01
【问题描述】:

我有一个自定义数据结构,可以通过多种方式访问​​它。我想尽量保持这个数据结构尽可能地符合 STL 标准。所以我已经有很多类型定义,它们为模板参数提供 STL 名称。现在对我来说一切照旧。

但是我不太确定如何正确地将迭代器添加到我的数据结构中。我面临的主要问题是,数据结构上会有多个迭代策略。最简单的用例是迭代所有元素,这将由符合 STL 的迭代器在数据结构上很好地处理。然而,人们可能还想访问在某种程度上类似于给定键的元素。我还想以一种可以与 STL 交互的方式迭代所有这些相似的元素。

这些是我目前想到的想法:

  • 只提供一种类型的迭代器

这基本上是std::map 所做的。子范围的开始和结束迭代器由std::map::lower_bound()std::map::upper_bound() 提供。

但是这很好用,因为begin()end()lower_bound()upper_bound() 返回的迭代器是兼容的,即operator==() 可以被赋予非常明确的含义。就我而言,这很难做到正确,甚至可能无法给出明确的语义。例如,我可能会遇到it1==it2++it1!=++it2 的情况。我不确定 STL 是否允许这样做。

  • 提供多种类型的迭代器

更容易提供干净的operator==() 语义。另一方面很讨厌,因为它增加了类型的数量。

  • 提供一种迭代器并使用 stl::algorithms 进行专门访问

我不确定这是否可能。迭代器应该以某种方式(直接或在备忘录中)保持迭代状态。使用这种方法意味着专门化所有 stl::algorithms 并在专门化中直接访问谓词。很可能是不可能的,如果可能的话,这是一个非常糟糕的主意。

现在我主要选择版本 1,即只提供一种类型的迭代器。但是由于我不清楚如何清理语义,所以我还没有决定。

你会怎么处理?

【问题讨论】:

    标签: c++ data-structures stl iterator


    【解决方案1】:

    为什么更多类型会成为问题?这并不一定意味着更多的代码。例如,您可以使您的迭代器类型成为一个模板,该模板将迭代策略作为模板参数。然后迭代策略可以提供迭代的实现:

    struct iterate_all_policy {
        iterate_all_policy(iterator<iterate_all_policy> & it) : it(it) {}
    
        void advance() { /* implement specific advance here */ }
    private:
        iterator<iterate_all_policy> & it;
    }
    

    您可能必须让迭代策略类成为迭代器类型的朋友。

    【讨论】:

    • 是的,在这种情况下,这主要是我的做法。其他迭代器基本上将成为模板实例化的 typedef。也许这个解决方案只是看起来不太好,因为没有一个 STL-Collections 是这样的(也许这只是不需要)。
    • @LiKao:标准容器不需要这样的机制,因为它们不支持不同的迭代策略。
    【解决方案2】:

    标准容器支持具有两种迭代器类型的两种迭代策略:::iterator::reverse_iterator。您可以使用std::reverse_iterator的构造函数及其成员函数base()在两者之间进行转换。

    根据您的迭代策略的相似程度,提供对不同迭代器类型的转换可能很容易,也可能不容易。这个想法是结果应该指向目标类型的迭代策略中的“等效位置”。对于反向迭代器,这种等价性的定义是,如果您在该点插入,结果是相同的。所以如果rit是一个反向迭代器,vec.insert(rit.base(), ...)会在rit的“之前”插入一个元素在反向迭代中,也就是说指向的元素之后由rit 在容器中。这是相当繁琐的,并且只会在迭代策略完全不相关时变得更糟。但是,如果您的所有迭代器类型都是(或看起来像)覆盖所有元素的“普通”迭代器的包装器,那么您可以根据该底层迭代器位置定义转换。

    只有在存在添加或删除容器元素的成员函数时,您实际上才需要 转换,因为您可能不想为每个迭代器类型提供单独的重载(就像标准容器没有为反向迭代器定义 inserterase)。如果迭代器仅用于指向元素,那么您很可能可以不用它们。

    如果不同的迭代策略都在元素子集上以正常顺序迭代,请查看boost::filter_iterator

    我可能会遇到it1==it2++it1!=++it2 的情况。我是 不确定 STL 是否允许这样做。

    如果我理解正确,您从thing.normal_begin() 开始得到it1,从thing.other_policy_begin() 开始得到it2。标准容器没有定义比较属于不同范围的相同类型的迭代器的结果,所以如果你确实使用了一个通用类型,那么我认为这会很好,只要文档清楚地表明虽然operator== 确实碰巧工作,范围根据迭代器的来源而分开。

    例如,您可以有一个skip_iterator,它将每次调用++ 时应该向前移动的步数作为构造函数参数。然后您可以在比较中包含该整数,以便thing.skip_begin(1) != thing.skip_begin(2),或者您可以排除它以便thing.skip_begin(1) == thing.skip_begin(2)++(++(thing.skip_begin(1))) == ++(thing.skip_begin(2))。我认为,只要记录在案就可以了,或者您可以记录比较它们是 UB,除非它们来自同一起点。

    【讨论】:

      猜你喜欢
      • 2012-04-16
      • 2016-11-15
      • 1970-01-01
      • 2019-08-23
      • 1970-01-01
      • 1970-01-01
      • 2019-06-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多