Что произойдет, если вы увеличите итератор, равный концевому итератору контейнера STL

Что делать, если я увеличиваю итератор на 2, когда он указывает на последний элемент вектора? В этом вопросе просим, ​​как настроить итератор на контейнер STL на 2 элемента, предлагаются два разных подхода:

  • либо использовать форму арифметического оператора - + = 2 или ++ дважды
  • или используйте std:: advance()

Я тестировал оба из них с VС++ 7 для края, когда итератор указывает на последний элемент контейнера STL или за его пределами:

vector<int> vec;
vec.push_back( 1 );
vec.push_back( 2 );

vector<int>::iterator it = vec.begin();
advance( it, 2 );
bool isAtEnd = it == vec.end(); // true
it++; // or advance( it, 1 ); - doesn't matter
isAtEnd = it == vec.end(); //false
it = vec.begin();
advance( it, 3 );
isAtEnd = it == vec.end(); // false

Я видел, может иногда совет сравнить с vector:: end() при перемещении вектора и других контейнеров:

for( vector<int>::iterator it = vec.begin(); it != vec.end(); it++ ) {
    //manipulate the element through the iterator here
}

Очевидно, что если итератор продвинулся за последний элемент внутри цикла, сравнение в выражении for-loop будет оцениваться как false, и цикл будет с удовольствием продолжен в undefined.

Правильно ли я понимаю, что если я когда-либо использую функцию advance() или любого типа операции увеличения на итераторе и заставляю ее указывать на конец контейнера, я не смогу обнаружить эту ситуацию? Если да, то в чем лучшая практика - не использовать такие улучшения?

Ответ 1

Ниже приводится цитата из книги Николая Йосуттиса:

Обратите внимание, что advance() не проверяет пересекает ли он конец() последовательности (он не может проверить, потому что итераторы вообще не знают контейнеры, на которых они работают). Таким образом, вызов этой функции может приводят к поведению undefined, потому что вызывающий оператор ++ для конца последовательность не определена

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

Ответ 2

Возможно, у вас должно быть что-то вроде этого:

template <typename Itr>
Itr safe_advance(Itr i, Itr end, size_t delta)
{
    while(i != end && delta--)
        i++;
    return i;
}

Вы можете перегрузить это, если iterator_category<Itr> есть random_access_iterator, чтобы сделать что-то вроде следующего:

return (delta > end - i)? end : i + delta;

Ответ 3

Вы можете использовать функцию "расстояние" между вашим итератором (it) и итератором в файле vec.begin() и сравнить его с размером вектора (полученным size()).

В этом случае цикл for будет выглядеть так:

for (vector<int>::iterator it = vec.begin(); distance(vec.begin(), it) < vec.size(); ++it)
{
     // Possibly advance n times here.
}

Ответ 4

Код, который предлагает Марийн, немного ошибочен (как указывал curiousguy).

Правильная версия последней строки:

bool isPastEnd = it >= vec.end();

Ответ 5

Я предлагаю вам взглянуть на Boost.Range.
Это может быть безопаснее использовать.
Он также будет в С++ 0x.

Ответ 6

container.end() - элемент после конца - это единственное определенное внешнее значение.

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

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

for(int i = 0; i < vec.size(); i+=2) {...}

Ответ 7

Вы также можете сделать больше сравнений в инструкции for:

for( vector<int>::iterator it = vec.begin(); it != vec.end() && it+1 != vec.end(); it+=2 ) {
    //manipulate the element through the iterator here
}

Я не знаю, как это могло бы выполнить vs предложение Kostas, но похоже, что было бы лучше для небольшого приращения. Конечно, это было бы довольно незаменимым для большого приращения, так как вам нужна проверка для каждого, но это еще один вариант.

Я бы определенно избегал этого, если это вообще возможно. Если вам действительно нужно увеличивать на 2 значения за раз, тогда рассмотрим наличие вектора std:: pair или вектора структуры с 2 элементами.

Ответ 8

Несмотря на то, что этот вопрос составляет полгода, может быть полезно упомянуть использование операторов сравнения > и < чтобы проверить, прошел ли вы повторное завершение (или начало при повторном возврате) контейнера. Например:

vector<int> vec;
vec.push_back( 1 );
vec.push_back( 2 );

vector<int>::iterator it = vec.begin();

it+=10; //equivalent to advance( it, 10 )
bool isPastEnd = it > vec.end(); //true