Testing whether an iterator points to the last item?

I have an stl iterator resulting from a std::find() and wish to test whether it is the last element. One way to write this is as follows:

mine *match = someValue;
vector<mine *> Mine(someContent);
vector<mine *>::iterator itr = std::find(Mine.begin(), Mine.end(), match);

if (itr == --Mine.end()) {
  doSomething;
}

But it seems to me that decrementing the end() iterator is asking for trouble, such as if the vector has no elements, then it would be undefined. Even if I know it will never be empty, it still seems ugly. I'm thinking that maybe rbegin() is the way to go, but am not certain as to best way to compare the forward iterator with a reverse iterator.

30
задан WilliamKF 18 August 2010 в 20:02
поделиться

7 ответов

Сделайте это:

// defined in boost/utility.hpp, by the way
template <typename Iter>
Iter next(Iter iter)
{
    return ++iter;
}

// first check we aren't going to kill ourselves
// then check if the iterator after itr is the end
if ((itr != Mine.end()) && (next(itr) == Mine.end()))
{
    // points at the last element
}

Вот и все. Никогда не дает неопределенного поведения, работает на всех итераторах, добрый день.

Завершите это для удовольствия:

template <typename Iter, typename Cont>
bool is_last(Iter iter, const Cont& cont)
{
    return (iter != cont.end()) && (next(iter) == cont.end())
}

Предоставление:

if (is_last(itr, Mine))

Если у вас аллергия на служебные функции / красивый код, сделайте:

if ((itr != Mine.end()) && (itr + 1 == Mine.end()))

Но вы не можете сделать это на итераторах без произвольного доступа. Этот работает с двунаправленными итераторами:

if ((itr != Mine.end()) && (itr == --Mine.end()))

И безопасен, поскольку при первой проверке end ()> itr .

55
ответ дан 27 November 2019 в 23:16
поделиться

Вот еще одно потенциальное решение:

template<class Iterator, class Container> bool is_last(Iterator it, const Container& cont)
{
    // REQUIREMENTS:
    // the iterator must be a valid iterator for `cont`
    if( it == cont.end() )
        return false;   // or throw if you prefer
    return (++it) == cont.end();
}
2
ответ дан 27 November 2019 в 23:16
поделиться

Да, уменьшать (или увеличивать) end небезопасно, если вектор может быть пустым. Делать то же самое с указателем даже несколько небезопасно, хотя, вероятно, вам это сойдет с рук.

Для большей безопасности используйте вычитание и значения, которые считаются безопасными и действительными:

if ( Mine.end() - itr == 1 )

Для совместимости со всеми прямыми итераторами (такими как в slist , в отличие от итераторов с произвольным доступом для ] vector и deque ), используйте

if ( std::distance( itr, Mine.end() ) == 1 )

или, если вы озабочены производительностью, но имеете двунаправленные итераторы (включая любой контейнер C ++ 03)

if ( itr != Mine.end() && itr == -- Mine.end() )

или действительно анальный случай только прямые итераторы и время O (1),

if ( itr != Mine.end() && ++ container::iterator( itr ) == Mine.end() )

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

if ( itr != Mine.end() && ++ ( Mine.begin() = itr ) == Mine.end() )
11
ответ дан 27 November 2019 в 23:16
поделиться

Почему вам нужно делать специальное поведение, только если элемент последний?

Как насчет этого. Планируется просто сравнить адрес элемента итератора с адресом последнего элемента в контейнере, с проверкой, чтобы убедиться, что элемент не является последним (что делает вызов back безопасным):

if (itr != Mine.end() && &*itr == &Mine.back()) {
  doSomething;
}
5
ответ дан 27 November 2019 в 23:16
поделиться

Если вы это сделаете:

if(itr != Mine.end() && itr == --Mine.end())

Все будет в порядке. Потому что, если itr не в конце, тогда в контейнере должен быть хотя бы 1 элемент, и поэтому конец должен давать результат значения при уменьшении.

Но если вам это все еще не нравится, есть много способов сделать что-то эквивалентное, как показывают все остальные ответы.

Вот еще одна альтернатива:

if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)
3
ответ дан 27 November 2019 в 23:16
поделиться

По сути, это та же проблема, что и при удалении узла из односвязного списка. У вас должно быть два итератора, один из которых следует за одним узлом за другим, поэтому, когда итератор «вперед» добирается до узла, который вы хотите удалить (или какой-либо другой операции; в вашем случае желаемый узел будет концом), " следующий итератор указывает на предыдущий узел (в вашем случае это будет последний узел).

1
ответ дан 27 November 2019 в 23:16
поделиться

Лучше всего скопировать итератор, а затем увеличить его. Затем вы можете протестировать увеличенную версию с помощью end () . Если вы будете осторожны, вы можете использовать постинкремент, чтобы избежать необходимости его формально копировать.

  if (++vector<mine*>::iterator(itr) == Mine.end())

Если бы это могло быть уже в конце:

  if (itr == Mine.end() || ++vector<mine*>::iterator(itr) == Mine.end())

Или, основываясь на ответе GMan, но немного безопаснее:

  if (Mine.Length() == 0 || itr == Mine.End() || &*itr == &Mine.back())

Я только что исправил последний, так как ошибался насчет & * .

1
ответ дан 27 November 2019 в 23:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: