Проверяет, указывает ли итератор на последний элемент? - PullRequest
30 голосов
/ 19 августа 2010

У меня есть итератор stl, полученный в результате std :: find (), и я хочу проверить, является ли он последним элементом. Один из способов написать это так:

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

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

Но мне кажется, что уменьшение итератора end () вызывает проблемы, например, если вектор не имеет элементов, он будет неопределенным. Даже если я знаю, что это никогда не будет пустым, это все еще кажется уродливым. Я думаю, что, возможно, rbegin () - это путь, но я не уверен, как лучше сравнить прямой итератор с обратным итератором.

Ответы [ 9 ]

55 голосов
/ 19 августа 2010

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

// 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 при первой проверке.

11 голосов
/ 19 августа 2010

Да, небезопасно уменьшать (или увеличивать) 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() )
5 голосов
/ 19 августа 2010

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

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

if (itr != Mine.end() && &*itr == &Mine.back()) {
  doSomething;
}
4 голосов
/ 26 апреля 2014

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

#include <iterator>
#include <type_traits>

template<typename Iter>
struct is_reverse_iterator : std::false_type { };

template<typename Iter>
struct is_reverse_iterator<std::reverse_iterator<Iter>>
: std::integral_constant<bool, !is_reverse_iterator<Iter>::value>
{ };

Тогда вы можете иметь два варианта выполнения теста

template<bool isRev> // for normal iterators
struct is_last_it
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .end()
        return it != cont.end() && ++it == cont.end();
    }
};

template<> // for reverse iterators
struct is_last_it<true>
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .rend()
        return it != cont.rend() && ++it == cont.rend();
    }
};

и одну интерфейсную функцию

template<typename It, typename Cont>
bool is_last_iterator(It it, Cont const &cont)
{
    return is_last_it<is_reverse_iterator<It>::value>::apply(it, cont);
};

Тогда для каждого типа итератора (обратный / прямой) вы можете использоватьинтерфейсная функция

int main()
{
    std::vector<int> v;
    v.push_back(1);

    auto it (v.begin()),  ite(v.end());   // normal iterators
    auto rit(v.rbegin()), rite(v.rend()); // reverse iterators

    std::cout << is_last_iterator(it, v) << std::endl;
    std::cout << is_last_iterator(ite, v) << std::endl;
    std::cout << is_last_iterator(rit, v) << std::endl;
    std::cout << is_last_iterator(rite, v) << std::endl;

    return 0;
}

Обратите внимание, что некоторые реализации (кроме достаточно распространенных std::begin() и std::end(), также включают std::rbegin() и std::rend(). По возможности используйте этот набор функций вместо члена.begin() и т. Д.

3 голосов
/ 19 августа 2010

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

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

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

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

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

if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)
2 голосов
/ 19 августа 2010

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

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();
}
1 голос
/ 01 марта 2017

Попытка сделать этот ответ максимально простым и универсальным:

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

Если итератор не является двунаправленным,

if( itr!=Min.end() && ++decltype(itr)(itr)==Mine.end())

Второй создает временную копию itr и увеличивает ее для проверки конечного итератора.

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

1 голос
/ 19 августа 2010

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

1 голос
/ 19 августа 2010

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...