Подтвердить что ты не робот

Можно ли преобразовать обратный итератор в форвардный итератор?

У меня есть класс под названием Action, который по существу является оберткой вокруг объекта Move.

Поскольку мне нужно пересечь deque 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, без необходимости спрашивать, иду я вперед или назад. Это означает, что одна функция возвращает один тип итератора, но у меня есть два типа.

Должен ли я забыть вернуть итератор и вместо этого вернуть ссылку const вместо объекта Move?

наилучшие пожелания,

BeeBand

4b9b3361

Ответ 1

Это именно та проблема, которая вызвала необходимость разработки STL. Существуют реальные причины:

  1. Не хранить итераторы вместе с контейнерами
  2. Использование алгоритмов, принимающих произвольные итераторы
  3. Алгоритмы оценивают весь диапазон, а не один элемент в время

Я подозреваю, что вы сейчас видите, это более или менее верхушка айсберга реальных проблем. Моим советом было бы сделать шаг назад, и вместо того, чтобы спрашивать о том, как справляться с деталями дизайна, как он сейчас стоит, задайте несколько более общий вопрос о том, что вы пытаетесь выполнить, и как лучше всего это сделать конечный результат.

Для тех, кто интересуется прежде всего вопросом в названии, ответ - это тяжело квалифицированное "да". В частности, обратный_тератор имеет член 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() в качестве параметров для общего алгоритма, это хорошо, но опыт показывает, что преобразование на итераторы вперед часто приводит к проблемам.

В конце концов, однако, для тела вопроса (в отличие от названия), ответ в значительной степени соответствует приведенному выше: проблема связана с попыткой создать объект, который объединяет коллекцию с парой итераторов в эту коллекцию. Исправьте эту проблему, и весь бизнес с итераторами вперед и назад становится спорным.

Ответ 2

Обратные итераторы имеют член base(), который возвращает соответствующий имперский итератор. Помните, что этот не итератор, который ссылается на один и тот же объект - он фактически ссылается на следующий объект в последовательности. Это значит, что rbegin() соответствует end(), а rend() соответствует begin().

Итак, если вы хотите вернуть итератор, тогда вы сделаете что-то вроде

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

Я бы предпочел вернуть ссылку, и инкапсулировать все детали итерации внутри класса.

Ответ 4

Мне кажется, что у вас на самом деле есть два разных поведения в одном классе.

Примечательно, что вы можете только перемещать свою коллекцию в одном порядке, иначе, если бы вы начали обход, а затем изменили аргумент bforward, вы оказались бы в довольно странной ситуации.

Лично я все могу разоблачить оба итератора (т.е. forward 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 чистым виртуальным интерфейсом и иметь как обратный, так и обратный обход.

Но на самом деле, я действительно не вижу смысла хранить BOTH вперед и назад итератор, если окажется, что вы будете использовать только один:/

Ответ 5

Возможно, вам следует переосмыслить свой выбор контейнера.

Обычно вам не нужно использовать обратные итераторы для возврата назад,

currentfwd--

будет идти назад, все, хотя это может не сработать (что я предполагаю, что вы попробовали) с dequeue.

То, что вы действительно должны сделать, - это моделировать свой класс здесь как декоратор dequeue и реализовать свои собственные итераторы Action. В любом случае я бы это сделал.