c ++ vector erase продвигает итератор - PullRequest
0 голосов
/ 25 октября 2019

Следующий упрощенный код работает, поскольку он удаляет все векторные элементы. Однако я не понимаю, почему. Поскольку f.erase(r) не захватывает возвращаемое значение, которое будет новым значением итератора, и нет другого инкрементатора итератора, и согласно документации, аргумент erase(iterator position) не передается по ссылке, гдеитератор продвинулся?

#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> f = {1,2,3,4,5};
  auto r = f.begin();
  while (r != f.end())
  {
    std::cout << "Erasing " << *r << std::endl;
    f.erase(r);
  }
  return 0;
}

Ответы [ 7 ]

2 голосов
/ 25 октября 2019

где итератор продвигается?

Нет, итератор остается в том же месте. Это технически неопределенное поведение, но если вы подумаете о том, что на самом деле делает цикл, вы поймете, почему вы получаете «правильные» результаты.

Ваш вектор содержит указатель на объекты, которые он хранит. Ваш итератор будет указывать на эту память со смещением на нужный элемент. В этом случае это будет указывать на начало данных. Когда вы удаляете первый элемент, итератор становится недействительным, но он по-прежнему указывает на начало вектора. erase перемещает все элементы вперед, поэтому при переходе к следующей итерации вы находитесь в том же состоянии, в котором находились на первой итерации, за исключением того, что вектор на один элемент меньше. Вы повторяете это до тех пор, пока не останется элементов, и end() == begin()

Вы не должны зависеть от того, что это всегда происходит, и вместо этого просто используйте clear(), чтобы удалить все элементы из вектора.

2 голосов
/ 25 октября 2019

Вы должны написать

  while (r != f.end())
  {
    std::cout << "Erasing " << *r << std::endl;
    r = f.erase(r);
   ^^^^^^^^^^^^^^^^
  }

, потому что после удаления итератор становится недействительным.

Или вы можете просто написать

f.clear();

, потому что цикл удаляет все элементывектора.

Примите во внимание, что поскольку итератор r используется только в цикле, лучше объявить его в области действия цикла, где он используется. например

  for ( auto r = f.begin(); r != f.end(); )
  {
    std::cout << "Erasing " << *r << std::endl;
    r = f.erase(r);
  }
2 голосов
/ 25 октября 2019

где итератор продвигается?

Итератор не продвигается. Тот факт, что ваш код, кажется, работает, просто случайно, и на самом деле у вас неопределенное поведение.

С cppreference на std::vector::erase:

Делает недействительными итераторыи ссылки в или после точки удаления, включая итератор end ().

Вы не можете использовать r после вызова f.erase(r);. Если вы это сделаете, могут случиться забавные вещи.

1 голос
/ 25 октября 2019

и согласно документации ,

Вы должны прочитать его до конца:

vector :: erase - C ++ Ссылка

Поскольку векторы используют массив в качестве основного хранилища, удаление элементов в позициях, отличных от end end , заставляет контейнер перемещать все элементы послеСегмент стер на свои новые позиции . Как правило, это неэффективная операция по сравнению с той же самой операцией, выполняемой для других типов контейнеров последовательностей (таких как list или forward_list ).

и

Возвращаемое значение

Итератор, указывающий на новое местоположение элемента, который следует за последним элементом, удаленным при вызове функции. Это конец контейнера , если операция стерла последний элемент в последовательности.

vector :: erase - C ++ Ссылка

И наконец:

Срок действия итератора

Итераторы, указатели и ссылки, указывающие на позицию (или first ) и далее, становятся недействительными, свсе итераторы, указатели и ссылки на элементы до position (или first ) гарантированно будут продолжать ссылаться на те же элементы, на которые ссылались до вызова.

1 голос
/ 25 октября 2019

Добавление / удаление элементов в / из вектора в большинстве случаев (включая erase ()) делает недействительными ссылки и итераторы. Использование старых итераторов приводит к неопределенному поведению.

Как отметил @Nathan в комментариях f.clear() - это все, что вам нужно.

0 голосов
/ 25 октября 2019

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

0 голосов
/ 25 октября 2019

erase делает недействительным r. Разыменование r после этого вызывает неопределенное поведение.


Но в реальном мире это не должно вызывать никаких проблем, если реализация намеренно не проверяет действительность итератора.

A vectorИтератор обычно сохраняет только указатель на элемент. При удалении элемента все элементы справа от него сдвигаются влево на одну позицию. Из-за этого ячейка памяти, которая раньше занимала удаленный элемент, будет занята следующим элементом.

...