Удаление STL не работает должным образом?
int main()
{
const int SIZE = 10;
int a[SIZE] = {10, 2, 35, 5, 10, 26, 67, 2, 5, 10};
std::ostream_iterator< int > output(cout, " ");
std::vector< int > v(a, a + SIZE);
std::vector< int >::iterator newLastElement;
cout << "contents of the vector: ";
std::copy(v.begin(), v.end(), output);
newLastElement = std::remove(v.begin(), v.end(), 10);
cout << "\ncontents of the vector after remove: ";
//std::copy(v.begin(), newLastElement, output);
//this gives the correct result : 2 35 5 26 67 2 5
std::copy(v.begin(), v.end(), output);
//this gives a 10 which was supposed to be removed : 2 35 5 26 67 2 5 2 5 10
cout << endl;
return 0;
}
В массиве a есть три десяти.
почему массив v содержит 10 после удаления всех 10 с функцией удаления.
вы можете увидеть скомпилированный вывод здесь
Ответы
Ответ 1
Фактически std::remove
не удаляет элемент из контейнера. Цитируется из здесь
Удалить удаляет из диапазона [first, last)
все элементы, которые равны value
. То есть remove возвращает итератор new_last
, так что диапазон [first, new_last)
не содержит элементов, равных value
. Итераторы в диапазоне [new_last, last)
все все еще разыскиваются, но элементы, на которые они указывают, не указаны. Удалить является стабильным, что означает, что относительный порядок элементов, которые не равны значению, не изменяется. `
То есть std::remove
работает только с парой итераторов и ничего не знает о контейнере, который фактически содержит элементы. На самом деле, std::remove
не может знать базовый контейнер, потому что нет пути, который он может исходить от пары итераторов, чтобы узнать о контейнере, к которому принадлежат итераторы. Таким образом, std::remove
действительно не удаляет элементы, просто потому, что он не может. Единственный способ фактически удалить элемент из контейнера - вызвать функцию-член в этом контейнере.
Итак, если вы хотите удалить элементы, используйте Erase-Remove Idiom:
v.erase(std::remove(v.begin(), v.end(), 10), v.end());
Erase-Remove Idiom настолько распространен и полезен, что std::list
добавил еще одну функцию-член, называемую list::remove
, которая производит то же самое эффект, аналогичный идиоме erase-remove
.
std::list<int> l;
//...
l.remove(10); //it "actually" removes all elements with value 10!
Это означает, что при работе с std::list
вам не нужно использовать idiom erase-remove
. Вы можете напрямую вызвать его функцию-член list::remove
.
Ответ 2
Причина в том, что алгоритмы STL не изменяют размер последовательности. remove
вместо фактического стирания элементов, перемещает их и возвращает итератор в "новый" конец. Затем этот итератор может быть передан функции члена erase
вашего контейнера для фактического выполнения удаления:
v.erase(std::remove(v.begin(), v.end(), 10), v.end());
Кстати, это называется "erase-remove idiom".
РЕДАКТИРОВАТЬ: Я был некорректен. См. Комментарии, и ответ Наваза.
Ответ 3
Поскольку std::remove
фактически не уменьшает размер контейнера, он просто перемещает все элементы вниз, чтобы заполнить место, используемое элементом "удалить". Например, если у вас есть последовательность 1 2 3 4 5
и используйте std::remove
для удаления значения 2
, ваша последовательность будет выглядеть как 1 3 4 5 5
. Если вы затем удалите значение 4
, вы получите 1 3 5 5 5
. Ни в коем случае никогда не говорят, что последовательность будет короче.