Проверка того, указывает ли итератор на последний элемент?
У меня есть 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() запрашивает проблемы, например, если у вектора нет элементов, тогда это будет undefined. Даже если я знаю, что он никогда не будет пустым, это все равно кажется уродливым. Я думаю, что, возможно, rbegin() - это путь, но я не уверен, что лучший способ сравнить итератор с обратным итератором.
Ответы
Ответ 1
Сделайте это:
// 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
}
Вот и все. Никогда не дает вам поведения undefined, работает на всех итераторах, хороший день.
Оберните это для удовольствия:
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
по первой проверке.
Ответ 2
Да, это небезопасно для уменьшения (или увеличения) end
, если вектор может быть пустым. Это даже несколько небезопасно, чтобы сделать то же самое с указателем, хотя вы, вероятно, сойдете с ним.
Чтобы быть действительно безопасным, используйте вычитание и значения, которые, как известно, являются безопасными и действительными:
if ( Mine.end() - itr == 1 )
Для совместимости со всеми итераторами вперед (например, в slist
, в отличие от итераторов с произвольным доступом из vector
и deque
) используйте
if ( std::distance( itr, Mine.end() ) == 1 )
или если вы заинтересованы в производительности, но имеете двунаправленные итераторы (включая любой контейнер С++ 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() )
Ответ 3
Зачем вам нужно выполнять специальное поведение, только если элемент является последним?
Как насчет этого. План состоит в том, чтобы сравнить адрес элемента итератора с адресом последнего элемента в контейнере с проверкой, чтобы убедиться, что элемент на самом деле еще не закончился (что делает безопасный вызов back
):
if (itr != Mine.end() && &*itr == &Mine.back()) {
doSomething;
}
Ответ 4
Вам сначала понадобится способ определить, является ли итератор обратным, который был изобретательно показано здесь:
#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);
};
Затем для каждого типа итератора (reverse/straight) вы можете использовать функцию интерфейса
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()
. По возможности используйте этот набор функций вместо member .begin()
и т.д.
Ответ 5
Если вы выполните:
if(itr != Mine.end() && itr == --Mine.end())
Все должно быть хорошо. Поскольку, если itr не находится в конце, то в контейнере должно быть не менее 1 элемента, поэтому конец должен давать результат при уменьшении.
Но если вам все еще не нравится, существует множество способов сделать что-то эквивалентное, как показывают все остальные ответы.
Здесь другая альтернатива:
if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)
Ответ 6
Здесь другое потенциальное решение:
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();
}
Ответ 7
Лучшим способом было бы скопировать итератор, а затем увеличить его. Затем вы можете протестировать увеличенную версию с помощью 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())
Я только что исправил последний, так как ошибся в &*
.
Ответ 8
Это по существу та же проблема, что и удаление node из односвязного списка. У вас должно быть два итератора, один из которых следует за node за другим, поэтому, когда "вперед" итератор попадает в node, который вы хотите удалить (или любую другую операцию; в вашем случае желаемый node будет конец), "следующий" итератор указывает на node раньше (в вашем случае это будет последний node).
Ответ 9
Попытка сделать этот ответ максимально простым и универсальным:
if( itr!=Mine.end() && itr== --Mine.end())
Если итератор не двунаправлен,
if( itr!=Min.end() && ++decltype(itr)(itr)==Mine.end())
Второй создать временную копию itr и прирастить его для проверки на итератор конца.
В обоих случаях первый тест позволяет избежать пустых контейнеров, чтобы вызвать ситуацию undefined.