4. Элемент v[5]
исключается, поэтому алгоритм игнорирует его и переходит к v[6]
. При этом он продолжает помнить, что на месте v[4]
остается «дыра», которую нужно заполнить.
5. Элемент v[6]
сохраняется, поэтому алгоритм присваивает v[6]
элементу v[4]
, вспоминает, что следующая «дыра» находится на месте v[5]
, и переходит к v[7]
.
6. Аналогичным образом анализируются элементы v[7]
, v[8]
и v[9]
. Значение v[7]
присваивается элементу v[5]
, а значение v[8]
присваивается элементу v[6]
. Элемент v[9]
игнорируется, поскольку находящееся в нем значение подлежит удалению.
7. Алгоритм возвращает итератор для элемента, следующего за последним «оставшимся». В данном примере это элемент v[7]
.
Перемещения элементов в векторе v
выглядят следующим образом:
Как объясняется в совете 33, факт перезаписи некоторых удаляемых значений имеет важные последствия в том случае, если эти значения являются указателями. Но в контексте данного совета достаточно понимать, что remove
не удаляет элементы из контейнера, поскольку в принципе не может этого сделать. Элементы могут удаляться лишь функциями контейнера, отсюда следует и главное правило настоящего совета: чтобы удалить элементы из контейнера, вызовите erase
после remove
.
Элементы, подлежащие фактическому удалению, определить нетрудно — это все элементы исходного интервала, начиная с нового «логического конца» интервала и завершая его «физическим» концом. Чтобы уничтожить все эти элементы, достаточно вызвать интервальную форму erase
(см. совет 5) и передать ей эти два итератора. Поскольку сам алгоритм remove
возвращает итератор для нового логического конца массива, задача решается прямолинейно:
vector v; //См. ранее
…
v.erase(remove(v.begin(), v.end(), 99), v.end());// Фактическое удаление
// элементов со значением 99
cout << v.size(); // Теперь выводится 7
Передача в первом аргументе интервальной формы erase
возвращаемого значения remove
используется так часто, что рассматривается как стандартная конструкция. Remove
и erase
настолько тесно связаны, что они были объединены в функцию remove
контейнера list
. Это единственная функция STL с именем remove
, которая производит фактическое удаление элементов из контейнера:
list li; // Создать список
… // Заполнить данными
li.remove(99);// Удалить все элементы со значением 99.
// Команда производит фактическое удаление
// элементов из контейнера, поэтому размер li
// может измениться
Честно говоря, выбор имени remove
в данном случае выглядит непоследовательно. В ассоциативных контейнерах аналогичная функция называется erase
, поэтому в контейнере list
функцию remove
тоже следовало назвать erase
. Впрочем, этого не произошло, поэтому остается лишь смириться. Мир, в котором мы живем, не идеален, но другого все равно нет. Как упоминается в совете 44, для контейнеров list
вызов функции remove
более эффективен, чем применение идиомы erase/remove
.
Как только вы поймете, что алгоритм remove
не может «по-настоящему» удалять объекты из контейнера, применение его в сочетании с erase
войдет в привычку. Не забывайте, что remove
— не единственный алгоритм, к которому относится это замечание. Существуют два других remove-подобных алгоритма: remove_if
и unique
.
Сходство между remove
и remove_if
настолько прямолинейно, что я не буду на нем останавливаться, но алгоритм unique
тоже похож на remove
. Он предназначен для удаления смежных повторяющихся значений из интервала без доступа к контейнеру, содержащему элементы интервала. Следовательно, если вы хотите действительно удалить элементы из контейнера, вызов unique
должен сопровождаться парным вызовом erase
. В контейнере list
также предусмотрена функция unique
, производящая фактическое удаление смежных дубликатов. По эффективности она превосходит связку erase-unique
.
Совет 33. Будьте внимательны при использовании remove-подобных алгоритмов с контейнерами указателей
Читать дальше