【问题标题】:Easiest way to make a cyclic iterator (circulator)?制作循环迭代器(循环器)的最简单方法?
【发布时间】:2009-11-23 09:29:04
【问题描述】:

我有一个对象,我想在游戏中连续循环旅行。我在 std::vector 中有一系列坐标,我想将其用作航点。

有没有办法让std::vector<T>::iterator 循环(也称为循环器)?

我能想到的最好方法是拥有两个迭代器,然后每当第一个迭代器用尽时,将第二个迭代器的值分配给它(它不会用于做任何其他事情),但我什至不确定它是否会工作 - 赋值运算符将复制迭代器用于保存索引的任何内容还是仅被引用(因此在第二轮之后将无用)?

我希望对象永远在路径点行进(除非它被销毁,但在该方法中不会发生这种情况),但迭代器只会为每一帧调用一次并且必须返回,以便我可以更新其他对象在游戏中。

该解决方案必须适用于 gcc 和 microsoft 编译器(如果无法用标准 C++ 编写)。

【问题讨论】:

  • 我已经写了一个这样的迭代器,所以它绝对可能 =) 我记得唯一的转折是比较运算符
  • 另一方面,你真的需要operator< 吗?隐藏循环行为有点恶作剧!

标签: c++ stdvector


【解决方案1】:

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

看看 boost::iterator_facade 和 boost::iterator 适配器。他们实现了完整的迭代器接口和你的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 以少量函数实现了完整的迭代器接口。它使用传递给iterator_adaptor基类的基迭代器为increment()decrement()advance()distance_to()equal_to()dereference()提供默认实现。

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

【讨论】:

  • +1: iterator_adaptor 是编写迭代器的最简单方法,示例非常棒(特别是如何只编写一个适配器并同时获得 const 和非 const 版本)
  • 有没有办法让像!= 这样的二元运算符可以处理cycle_iterator&lt;IteratorBase&gt;IteratorBase 类型的参数?我正在尝试比较 cycle_iterator 和“常规”迭代器,在这种情况下需要进行显式重载。
  • increment() need work methinks -- 我认为理想情况下,在end 之前增加值应该给你beginning,而不是循环迭代器上的end
  • 为了扩展@Yakk 注释,当前提议的公共方法void increment() 的实现可能会导致对m_itEnd 的取消引用,例如在循环循环中使用。由于end 迭代器指针通常指向末尾的地址,所以increment() 方法应该先递增,然后检查它是否超出末尾,例如:void increment() { ++base_reference(); if(base_reference() == m_itEnd) base_reference()=m_itBegin; }
【解决方案2】:

boost::iterator adaptor 是要走的路,相信我的话 ;)

话虽如此,我想指出一些陷阱。我认为我无法编辑现有答案,请耐心等待。

鉴于您的基本迭代器将是一个向量,您需要注意您需要实现哪些核心接口函数。如果您希望 cycle_iterator 成为随机访问迭代器,您需要以下所有内容:

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

现在distance_to(j) 对于cycle_iterator 来说是一个有点有趣的概念,它的语义会给你带来各种麻烦。这可以通过将适配迭代器的迭代器类别限制为正向或双向来避免。像这样:

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;
  }
}

对于双向,您还需要递减:

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

免责声明:我没有通过编译器运行它,所以我准备好尴尬了。

【讨论】:

  • 我认为通常distance_to(j) 可以返回正数或负数,当添加到迭代器时,会产生一个指向j 的迭代器。您可能决定要返回最小的非负数,或具有最小绝对值的数字。在大多数情况下,后者可能最有意义 - 或者是否有强有力的论据(反例)?
  • 任何使循环迭代器成为随机访问的尝试都注定要失败。 Random access iterator requirements 与循环迭代概念不兼容。特别是,没有办法在循环迭代器上建立严格的全序。
【解决方案3】:

从 std::vector 派生您自己的集合,并提供您自己的迭代器实现来覆盖递增和递减运算符。

网上有很多教程。比如看看this blog post

【讨论】:

  • 从向量派生?你曾经尝试过吗?我不同意。改为聚合!
  • 我从未见过从标准容器派生的类更适合它。聚合。
  • 我同意 cmets。响应匆忙发布,闲暇时忏悔。但是,不能删除它,因为它已被接受为答案。
  • 关于从标准容器派生stackoverflow.com/questions/922248/… 有单独的讨论。在我看来,从容器派生的危险被夸大了。反对的例子涉及派生和添加成员,这不是这里的情况。私有继承也是一种可能。
猜你喜欢
  • 1970-01-01
  • 2011-10-24
  • 2012-07-20
  • 1970-01-01
  • 1970-01-01
  • 2012-01-21
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
相关资源
最近更新 更多