制作循环迭代器(循环器)的最简单方法是什么?

14

我有一个对象,希望在游戏中以连续循环的方式移动。我有一系列坐标在一个 std::vector 中,希望将其用作路径点。

有没有办法使 std::vector<T>::iterator 成为循环迭代器(也称为圆周迭代器)?

最好的方法是使用两个迭代器,当第一个迭代器被耗尽时将其赋值为第二个迭代器(第二个迭代器不会用于其他操作),但我甚至不确定它是否有效 - 赋值运算符是否会复制迭代器用于保存索引的任何内容,还是仅仅是引用(因此在第二轮后将是无效的)?

我希望对象永久地按照路径点运动(除非它被销毁,但这在该方法中不会发生),但每一帧迭代器只会被调用一次,并且必须返回以便我可以更新游戏中的其他对象。

这个解决方案必须在gcc和microsoft编译器上工作(如果无法在标准C++中编写)。


我编写了这样的迭代器,所以肯定是可能的 =) 我记得唯一的变化是比较运算符“<”应始终返回true,除非迭代器相等。 - Viktor Sehr
1
另一方面,你真的需要 operator< 吗?隐藏循环行为有点淘气! - Matthieu M.
3个回答

23

好的,现在你的问题更加清晰了 :-)

看一下 boost::iterator_facade 和 boost::iterator adaptor。它们实现了完整的迭代器接口,而你的 cycle_iterator 只需实现一些方法,如 increment()、decrement():

template<class IteratorBase>
class cycle_iterator 
     : public boost::iterator_adaptor< 
          cycle_iterator,     // the derived class overriding iterator behavior
          IteratorBase,       // the base class providing default behavior
          boost::use_default, // iterator value type, will be IteratorBase::value_type
          std::forward_iterator_tag, // iterator category
          boost::use_default  // iterator reference type
       > 
{
  private:
     IteratorBase m_itBegin;
     IteratorBase m_itEnd;

  public:
     cycle_iterator( IteratorBase itBegin, IteratorBase itEnd ) 
       : iterator_adaptor_(itBegin), m_itBegin(itBegin), m_itEnd(itEnd)
     {}

     void increment() {
        /* Increment the base reference pointer. */
        ++base_reference();

        /* Check if past-the-end element is reached and bring back the base reference to the beginning. */
        if(base_reference() == m_itEnd)
            base_reference() = m_itBegin;
     }

     // implement decrement() and advance() if necessary
  };

这段代码可能无法编译,但可以帮助你开始。

编辑:

boost::iterator_adaptor使用少量函数实现了完整的迭代器接口。它提供了对increment()decrement()advance()distance_to()equal_to()dereference()的默认实现,这些函数使用传递给iterator_adaptor基类的基础迭代器。

如果您只需要一个前向迭代器,则必须实现increment()方法以在到达结束迭代器时绕回。如果以类似方式实现decrement(),则循环迭代器可以是双向的。如果IteratorBase本身是随机访问迭代器,则循环迭代器也可以是随机访问的,方法advancedistance_to必须使用模运算进行实现。


“iterator_adaptor” 是编写迭代器最简单的方法,而且示例非常棒(特别是如何仅编写一个适配器并获取 const 和非 const 版本)。 - Matthieu M.
有没有办法让像!=这样的二进制运算符适用于类型为cycle_iterator<IteratorBase>IteratorBase的参数?我正在尝试比较cycle_iterator和“常规”迭代器,并需要在这种情况下进行显式重载。 - Mikhail
1
increment() 需要改进,我认为理想情况下,在循环迭代器中在 end 之前递增值应该给出 begin 而不是 end - Yakk - Adam Nevraumont
为了扩展@Yakk的评论,当前建议的公共方法void increment()的实现可能会导致对m_itEnd的取消引用,例如在循环周期中使用。由于end迭代器指针通常指向结尾后面的地址,因此increment()方法应该先进行增量,然后再检查是否超过了结尾,即像这样: void increment() { ++base_reference(); if(base_reference() == m_itEnd) base_reference()=m_itBegin; } - Claudio

8

boost::iterator适配器是最好的选择,相信我的话;)

话虽如此,我想指出一些陷阱。我不认为我可以编辑现有的答案,所以请容忍我。

考虑到你的基本迭代器将是一个向量,你需要小心哪些核心接口函数需要实现。如果你希望你的cycle_iterator成为一个随机访问迭代器,你需要实现以下所有函数:

increment() 
decrement()
advance(n)
distance_to(j)

现在,对于一个循环迭代器来说,distance_to(j)这个概念有点滑稽,其语义可能会带来各种问题。通过将适配后的迭代器的迭代器类别限制为前向或双向,可以避免这种情况。像这样:

template <class BaseIterator>
class cycle_iterator
  : public boost::iterator_adaptor<
        cycle_iterator                  // Derived
      , BaseIterator                    // Base
      , boost::use_default              // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
    >
{ ... };

在这种情况下,您只需要实现增量:
void increment()
{
  if (++this->base_reference() == this->m_itEnd)
  {
    this->base_reference() = this->m_itBegin;
  }
}

对于双向迭代器,您还需要使用“decrement”:

void decrement()
{
  if (this->base_reference() == this->m_itBegin)
  {
    this->base_reference() = this->m_itEnd;
  }
  --this->base_reference();
}

免责声明:我没有通过编译器运行此代码,因此可能会出现错误。


我认为通常情况下,distance_to(j) 可以返回一个正数或负数,当加到迭代器上时,会指向 j。你可能会决定只返回最小的非负数,或者最小绝对值的数。在大多数情况下,后者可能更有意义 - 或者是否有强有力的反例(反证)? - Kuba hasn't forgotten Monica
任何试图将循环迭代器变为随机访问迭代器的尝试都是注定失败的。随机访问迭代器要求与循环迭代概念不兼容。特别是,无法在循环迭代器上建立严格的全序关系。 - Evg

-4
从std::vector派生出自己的集合,并提供自己的迭代器实现,覆盖递增和递减运算符。
网络上有很多教程。例如,可以查看这篇博客文章

3
从向量导出?你尝试过吗?我不同意。相反,聚合起来! - xtofl
我从未见过从标准容器派生出的类对其更有益处。集合。 - deft_code
1
我同意评论。回复匆忙发布,后悔莫及。但是,无法删除它,因为它已被接受为答案。 - Seb Rose
有关从标准容器派生的讨论是分开的 https://dev59.com/f3NA5IYBdhLWcg3wh-cC。在我看来,从容器派生的危险被夸大了。反对这一点的例子涉及到从中派生和添加成员,而这里并非如此。私有继承也是一种可能性。 - alfC

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