Можно ли преобразовать обратный итератор в форвардный итератор?
У меня есть класс под названием 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
Ответы
Ответ 1
Это именно та проблема, которая вызвала необходимость разработки STL. Существуют реальные причины:
- Не хранить итераторы вместе с контейнерами
- Использование алгоритмов, принимающих произвольные итераторы
- Алгоритмы оценивают весь диапазон, а не один элемент в время
Я подозреваю, что вы сейчас видите, это более или менее верхушка айсберга реальных проблем. Моим советом было бы сделать шаг назад, и вместо того, чтобы спрашивать о том, как справляться с деталями дизайна, как он сейчас стоит, задайте несколько более общий вопрос о том, что вы пытаетесь выполнить, и как лучше всего это сделать конечный результат.
Для тех, кто интересуется прежде всего вопросом в названии, ответ - это тяжело квалифицированное "да". В частности, обратный_тератор имеет член 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();
}
Я бы предпочел вернуть ссылку, и инкапсулировать все детали итерации внутри класса.
Ответ 3
Так как std::deque
является контейнером произвольного доступа (так же, как std::vector
), вам намного лучше использовать единый целочисленный индекс в deque для обоих обходов.
Ответ 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. В любом случае я бы это сделал.