【问题标题】:Can I convert a reverse iterator to a forward iterator?我可以将反向迭代器转换为正向迭代器吗?
【发布时间】:2011-01-03 12:25:30
【问题描述】:

我有一个名为 Action 的类,它本质上是一个包装 Move 对象的双端队列。

因为我需要向前和向后遍历Moves 的双端队列,所以我有一个前向迭代器和一个reverse_iterator 作为类的成员变量。这样做的原因是因为我需要知道我什么时候已经超过了双端队列的“结束”,无论是前进还是后退。

类如下所示:

class Action
{
public:
    SetMoves(std::deque<Move> & dmoves) { _moves = dmoves; }
    void Advance();
    bool Finished() 
    {
        if( bForward )
            return (currentfwd==_moves.end());
        else
            return (currentbck==_moves.rend());
    }
private:
    std::deque<Move> _moves;
    std::deque<Move>::const_iterator currentfwd;
    std::deque<Move>::const_reverse_iterator currentbck;
    bool bForward;
};

Advance函数如下:

void Action::Advance
{
    if( bForward)
        currentfwd++;
    else
        currentbck++;
}

我的问题是,我希望能够检索到当前Move 对象的迭代器,而无需查询我是前进还是后退。这意味着一个函数返回一种类型的迭代器,但我有两种类型。

我应该忘记返回一个迭代器,而是返回一个对 Move 对象的 const 引用吗?

【问题讨论】:

  • 您的问题“我可以从前向迭代器中得到一个 reverse_iterator”的答案是here

标签: c++ iterator


【解决方案1】:

反向迭代器有一个成员base(),它返回一个对应的正向迭代器。请注意,这 不是 引用同一对象的迭代器 - 它实际上是指序列中的下一个对象。这样rbegin()对应end()rend()对应begin()

所以如果你想返回一个迭代器,那么你会做类似的事情

std::deque<Move>::const_iterator Current() const
{
    if (forward)
        return currentfwd;
    else
        return (currentbck+1).base();
}

不过,我更愿意返回一个引用,并将所有迭代细节封装在类中。

【讨论】:

  • (currentbck+1).base() borks 当 currentbck 是一个结束迭代器时。在两者之间转换是一个等待发生的错误世界。
【解决方案2】:

正是促使STL设计开始的那种问题。有真正的原因:

  1. 不将迭代器与容器一起存储
  2. 使用接受任意迭代器的算法
  3. 让算法评估整个范围,而不是一次评估单个项目

我怀疑您现在看到的或多或少只是真正问题的冰山一角。我的建议是退后一步,而不是问如何处理目前的设计细节,而是问一个更笼统的问题,关于你想要完成什么,以及如何最好地完成它最终结果。

对于那些主要关心标题中问题的人来说,答案是非常合格的“是”。特别是,reverse_iterator 有一个 base() 成员来执行此操作。资格虽然有些问题。

演示问题,考虑如下代码:

#include <iostream>
#include <vector>
#include <iterator>

int main() { 
    int i[] = { 1, 2, 3, 4};
    std::vector<int> numbers(i, i+4);

    std::cout << *numbers.rbegin() << "\n";
    std::cout << *numbers.rbegin().base() << "\n";
    std::cout << *(numbers.rbegin()+1).base() << "\n";

    std::cout << *numbers.rend() << "\n";
    std::cout << *numbers.rend().base() << "\n";
    std::cout << *(numbers.rend()+1).base() << "\n";
}

在这个特定的时刻在我的特定机器上运行它会产生以下输出:

4
0
4
-1879048016
1
-1879048016

总结:对于rbegin(),我们必须在转换为前向迭代器之前添加一个以获得有效的迭代器——但对于rend(),我们必须添加在转换之前获得一个有效的迭代器。

只要您使用X.rbegin()X.rend() 作为通用算法的参数,就可以了——但经验表明,转换为前向迭代器通常会导致问题。

然而,最后,对于问题的主体(而不是标题),答案与上面给出的差不多:问题源于试图创建一个将集合与几个迭代器组合在一起的对象进入那个集合。解决这个问题,使用正向和反向迭代器的整个业务就变得没有意义了。

【讨论】:

  • 我喜欢你的回答。你可能是对的。我对 C++ 和 STL 比较陌生。什么是好的 C++ 设计是我正在努力学习的东西。
  • 虽然这个答案对 BeeBand 有帮助,但它并没有回答后代的原始问题。这个答案应该是对原始帖子的评论。我会要求 BeeBand 考虑将刻度更改为 Mike Seymour 的答案。
  • @Lukasz:如果您将“问题”限制为标题中的内容,那么您是对的。但是,如果您阅读整个问题本身,我会说得更少(充其量)。
  • 四年后,我认为人们发现这个问题更多是因为它的标题而不是它的内容。
  • 又过了五年,更是如此^
【解决方案3】:

由于@987654321@random access container(与std::vector 相同),因此最好在双端队列中使用单个整数索引进行两次遍历。

【讨论】:

  • 谢谢 - 出于这个原因,我一直在应用程序的其余部分使用双端队列。我不知道为什么我对迭代器有狭隘的看法:-)
  • 这就是为什么你总是需要第二双眼睛:)
  • 但请注意:测试无符号整数以了解是否已达到低于零值非常困难;)
  • 您可以使用(前向)迭代器,注意不要在它等于 end() 时增加它,或者如果它等于 end() 则减少它。无论哪种方式,请注意使迭代器无效的操作,因为它们也可能使索引无效(因为它不再指向您认为它指向的元素,或者因为当索引指向双端队列的末尾时您删除了某些东西)。
  • 你的意思是如果等于 begin() 就不要递减吗?问题在于,如果 Advance() 没有转到与rend() 等效的功能,则在实际处理完所有移动后,像 GetCurrentMove() 这样的函数将返回 begin()。
【解决方案4】:

在我看来,您实际上在同一个班级中有两种不同的行为。

值得注意的是,您似乎只能以一种顺序遍历您的集合,否则如果您开始遍历然后更改 bforward 参数,您最终会遇到非常奇怪的情况。

就个人而言,我完全赞成公开这两个迭代器(即转发begin, end, rbegin and rend)。

你也可以返回一个简单的 Iterator 对象:

template <class T>
class Iterator
{
public:
  typedef typename T::reference_type reference_type;

  Iterator(T it, T end) : m_it(it), m_end(end) {}

  operator bool() const { return m_it != m_end; }

  reference_type operator*() const { return *m_it; }

  Iterator& operator++() { ++m_it; return *this; }

private:
  T m_it;
  T m_end;
};

template <class T>
Iterator<T> make_iterator(T it, T end) { return Iterator<T>(it,end); }

然后,您可以只返回这个简单的对象:

class Action
{
public:
  Action(std::deque<Move> const& d): m_deque(d) {} // const& please

  typedef Iterator< std::deque<Move>::iterator > forward_iterator_type;
  typedef Iterator< std::deque<Move>::reverse_iterator > backward_iterator_type;

  forward_iterator_type forward_iterator()
  {
    return make_iterator(m_deque.begin(), m_deque.end());
  }

  backward_iterator_type backward_iterator()
  {
    return make_iterator(m_deque.rbegin(), m_deque.rend());
  }


private:
  std::deque<Move> m_deque;
};

或者如果你想在前向和后向遍历之间动态选择,你可以让 Iterator 成为一个纯虚拟接口,并且同时具有前向和后向遍历。

但实际上,如果您似乎只使用一个迭代器,我并没有真正看到存储前向和后向迭代器的意义:/

【讨论】:

  • 我喜欢这个解决方案,它对我来说可能是一个很好的学习练习。存储两个迭代器的原因是因为“GetCurrentMove() 是从应用程序中的不同位置调用 Advance()。所以我需要一个方便的位置来存储“当前移动”。
  • 这通常是迭代器的作用,尽管在 C++ 中将它实现为 2 个不同的对象,虽然它节省了一些地方并模仿了指针运算,但我认为这很烦人。上面的迭代器的灵感来自 Python > 一个同时保持其当前位置和结束点的对象。在您的应用程序中,您不需要传递完整的Action 类,您只需要传递Iterator(或者如果您要直接引用双端队列迭代器、当前位置和结尾)。这样,您就可以促进解耦:)
【解决方案5】:

也许你应该重新考虑你对容器的选择。

通常你不需要使用反向迭代器来回退,

currentfwd--

会倒退,尽管它可能无法与 dequeue 一起工作(我假设你尝试过)。

您真正应该做的是在这里将您的类建模为 dequeue 的装饰器并实现您自己的 Action 迭代器。无论如何我都会这样做。

【讨论】:

  • 谢谢查尔斯。向后退的问题在于 Finished() 函数 - 我需要知道我何时是第一个元素之前的一个(即已经过去了 "rend()" )。
猜你喜欢
  • 1970-01-01
  • 2020-11-06
  • 2021-04-16
  • 2020-10-28
  • 2010-10-27
  • 2012-05-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多