Что происходит, если вы увеличиваете итератор, равный конечному итератору контейнера STL - PullRequest
60 голосов
/ 29 июня 2009

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

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

Я протестировал оба из них с VC ++ 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 будет иметь значение false, и цикл с радостью перейдет в неопределенное поведение.

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

Ответы [ 8 ]

60 голосов
/ 29 июня 2009

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

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

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

13 голосов
/ 29 июня 2009

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

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;
7 голосов
/ 29 июня 2009

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

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

for (vector<int>::iterator it = vec.begin(); distance(vec.begin(), it) < vec.size(); ++it)
{
     // Possibly advance n times here.
}
3 голосов
/ 02 февраля 2012

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

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

bool isPastEnd = it >= vec.end();
2 голосов
/ 30 июня 2009

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

1 голос
/ 29 июня 2009

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

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

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

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

1 голос
/ 29 июня 2009

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

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

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

для (int i = 0; i

0 голосов
/ 06 января 2010

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

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
...