Удаление 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. Ни в коем случае никогда не говорят, что последовательность будет короче.