Я написал некоторый код, который принимает итераторы, но должен сделать сравнение в обратном порядке,
template<class ConstBiIter>
bool func(ConstBiIter seq_begin, ConstBiIter seq_end)
{
ConstBiIter last = std::prev(seq_end);
while (--last != std::prev(seq_begin)) // --> I need to compare the beginning data
{
......
}
return true;
}
В VS2013 при работе в режиме отладки --last != std::prev(seq_begin)
приведет к ошибке утверждения отладчика с сообщением об ошибке
Expression:string iterator + offset out of range.
но это нормально при работе в режиме Release и выдаче правильного результата, потому что в режиме Released нет проверки границы.
Мои вопросы:
Безопасно ли использовать std::prev(some_container.begin())
как сторож some_container.rend()
?
Как я могу напрямую сравнить reverse_iterator
с iterator
? Если я напишу код:
std::cout << (std::prev(some_container.begin())==some_container.rend()) << std::endl;
он не скомпилируется, даже если вы reinterpret_cast
их.
Мне любопытно, если prev(some_container.begin())
равняется some_container.rend()
физически?
Неопределенное поведение небезопасно, даже если оно работает сегодня в вашем тесте. В C ++ «это сработало, когда я попробовал», не является хорошим доказательством того, что вы делаете это правильно: один из наиболее распространенных типов неопределенного поведения — «это похоже на работу».
Проблема в том, что работа с неопределенным поведением принципиально хрупкая. Это может сломаться, если ты дышишь на нем.
Компилятор может оптимизировать ветки и код, доступ к которым возможен только благодаря неопределенному поведению, и во многих случаях делает именно это. Это даже можно сделать бесплатно после исправления службы, обновления компилятора, несущественного изменения флагов, передаваемых компилятору, или длины пути к исполняемому файлу. Он может работать нормально 99,9% времени, а затем отформатировать жесткий диск остальные 0,1% времени.
Некоторые из них более вероятны, чем другие.
Пока итераторы std::string
а также std::vector
элементы в основном являются указателями в релизе, и компилятор может даже определить указатель, который будет назван итераторами, даже если это предположение может быть ошибочным, когда в следующей версии компилятора используются переносимые указатели.
Неопределенное поведение оставлено в стандарте C ++, чтобы позволить авторам компилятора генерировать более оптимальный код. Если вы вызываете это, вы можете наступить на их пальцы.
При этом есть причины использовать поведение, не определенное стандартом C ++. Когда вы это сделаете, документально зафиксируйте его, изолируйте и убедитесь, что результат (скажем, делегаты в два раза быстрее std::function
) стоит того.
Вышеуказанное не является изолированным и не стоит выполнять неопределенное поведение, особенно потому, что вы можете решить его без неопределенного поведения.
Самое простое решение, если вы хотите выполнить итерацию в обратном направлении, — это создать несколько обратных итераторов.
template<class ConstBiIter>
bool func(ConstBiIter seq_begin, ConstBiIter seq_end)
{
std::reverse_iterator<ConstBiIter> const rend(seq_beg);
for (std::reverse_iterator<ConstBiIter> rit(seq_end); rit != rend; ++rit)
{
......
}
return true;
}
Сейчас rfirst
перебирает диапазон в обратном направлении.
Если вам нужно вернуться к прямому итератору, который по какой-либо причине ссылается на один и тот же элемент, и вы не rend
, вы можете std::prev(rit.base())
, Если rit == seq_end
в этот момент это неопределенное поведение.
Нет, попытаться уменьшить начальный итератор небезопасно.
std::reverse_iterator
(это то, что возвращается std::rend
) фактически не содержит итератор перед начальным итератором. Он сохраняет основной итератор для следующего элемента из того, на который он концептуально указывает. Следовательно, когда обратный итератор «один за концом» (т.е. «перед началом»), его основной итератор (который вы получите, вызвав base()
) является начальным итератором.
24.5.1 Обратные итераторы
Шаблон класса reverse_iterator — это адаптер итератора, который выполняет итерацию от конца последовательности, определенной его основным итератором, до начала этой последовательности. Фундаментальное соотношение между обратным итератором и его соответствующим итератором i определяется тождеством: &* (reverse_iterator (i)) == &* (я — 1).