Подтвердить что ты не робот

Std:: удалить с помощью vector:: erase и undefined поведение

Во всем Интернете я вижу, что люди используют удалить/удалить идиому для векторов С++, например:

#include <vector> // the general-purpose vector container
#include <iostream>
#include <algorithm> // remove and remove_if
int main()
{
  // initialises a vector that holds the numbers from 0-9.
  std::vector<int> v = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

  // removes all elements with the value 5
  v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );

  return 0;
}

То есть, если я хочу удалить все элементы, соответствующие некоторым критериям (например, число 5 из вектора int s), я использую std::remove или std::remove_if в сочетании с vector.erase следующим образом:

vector.erase( std::remove( vector.begin(), vector.end(), <some_value>), vector.end());

Это хорошо работает в целом; std::removeremove_if) скопирует (или использует семантику перемещения в С++ 11) элементы, которые должны быть удалены до конца вектора, поэтому вектор из нашего предыдущего примера теперь будет выглядеть так:

{0, 1, 2, 3, 4, 6, 7, 8, 9, 5};

С элементом 5 полужирным шрифтом, потому что он был перенесен в конец.

Теперь std::remove вернет ему итератор, который мы затем используем в erase, чтобы очистить элементы. Ницца.

Но как насчет следующего примера?

int main()
{
  // initialises an empty vector.
  std::vector<int> v = {};

  // removes all elements with the value 5
  v.erase( std::remove( v.begin(), v.end(), 5 ), v.end() );

  return 0;
}

Кажется, что это работает как ожидалось (не стирая ничего, а не segfaulting и т.д.) на всех платформах, на которых я запускаю его, но я знаю, что только потому, что что-то работает, это не значит, что это не поведение undefined.

Быстрое reference для vector.erase говорит об этом (основное внимание):

iterator erase (const_iterator first, const_iterator last);

first, last являются

Итераторы, определяющие диапазон внутри вектора]: [first,last). то есть диапазон включает в себя все элементы между first и last, , включая элемент, указанный первым, но не тот, на который указывает last.  Типы участников iterator и const_iterator являются типами итераторов произвольного доступа, которые указывают на элементы.

Итак, поведение vector.erase(vector.end(),vector.end()) undefined?

Вот что говорится в быстрой ссылке о безопасности исключений:

Если удаленные элементы содержат последний элемент в контейнере, никаких исключений не выбрасывается (гарантия отсутствия броска).  В противном случае контейнер, как гарантируется, должен быть закончен в действительном состоянии (основная гарантия).  Недопустимый position или range вызывает поведение undefined.

Итак, ответ, по крайней мере, мне кажется "ДА", и qaru.site/info/210702/..., похоже, его поддерживает.

Следовательно, является ли распространенная идиома неправильной?

Предполагая, что это поведение undefined, любой вызов remove мог бы вернуть итератор в vector.end(), который должен быть проверен перед вызовом vector.erase, и вызов remove на пустой вектор, кажется, возвращает vector.end: (IDEOne для кода ниже)

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
   vector<int> myInts;
   auto anIter = std::remove(myInts.begin(),myInts.end(),5);
   if (anIter == myInts.end())
      std::cout << "iterator = myInts.end()";
}

Наконец, мой вопрос:

Должен ли быть фактический идентификатор удаления/стирания?

auto endOfRangeIterator = std::remove(vector.begin(), vector.end(), <value>);
if (endOfRangeIterator != vector.end())
   vector.erase(endOfRangeIterator, vector.end())
4b9b3361

Ответ 1

24.2.1/7. Большинство алгоритмических шаблонов библиотеки, которые работают с структурами данных, имеют интерфейсы, которые используют диапазоны. Диапазон - это пара итераторов, которые обозначают начало и конец вычисления. Диапазон [i,i) - пустой диапазон; в общем случае диапазон [i,j) относится к элементам в структуре данных, начиная с элемента указана на i и до, но не включая элемент, на который указывает на j.

Акцент на мой.

Кроме того, описание erase, которое вы цитируете, не является нормативным текстом в стандарте. Стандарт должен сказать это (Таблица 100):

a.erase(q1,q2)

Эффекты: стирает элементы в диапазоне [q1, q2).

Это не требует, чтобы q1 был разыменован. Если [q1, q2] - пустой диапазон (в 24.2.1/7), то никакие элементы не находятся в диапазоне, и поэтому ни один из них не стирается.

Ответ 2

Итак, поведение vector.erase(vector.end(), vector.end()) undefined?

Нет. Из-за утверждения рядом с тем, которое вы создали:

Итераторы, определяющие диапазон внутри вектора], которые будут удалены: [первый, последний]. то есть диапазон включает в себя все элементы между первым и последним, включая элемент, указанный первым , но не тот, который указан последним.

Итак, vector.erase(vector.end(),vector.end()) не пытается удалить vector.end(), потому что на него указывает параметр last.

Конечно, это определение неоднозначно, и эти утверждения можно интерпретировать как противоречивые. Указанная формулировка не используется стандартом.

Ответ 3

Я думаю, что более важным в вашем цитировании является:

Итераторы, определяющие диапазон внутри вектора], которые необходимо удалить: [первый Последний). то есть диапазон включает в себя все элементы между первыми и последний, включая элемент, указанный первым , но не тот указана последним. Типы итераторов типов и const_iterator являются случайными доступ к типам итераторов, которые указывают на элементы.

Как мы нашли в комментариях, эта цитата из cpluspluc.com неверна. Это не будет нарушать правила в случае ( v.end, v.end), но будет неправильным в случае

#include <vector>

int main()
{
    std::vector<int> v = { 1, 2, 3 };

    v.erase( v.begin(), v.begin());
}

поскольку утверждение, противоречащее себе с

диапазон включает (...), включая элемент, на который указывает v.begin() , но не тот, на который указывает v.begin().

не может быть допустимым оператором.

С++ Стандарт n3337 в § 23.2.2 Требования к контейнерам последовательностей В таблице 100 указано, что

a.erase(q1,q2) возвращает iterator. Обратите внимание:

Требуется: для вектора и дека Т должен быть MoveAssignable. Последствия: Стирает элементы в диапазоне [q1, q2).

И вот что он говорит о диапазоне [i,j) в § 24.2.1/7 Требования к итератору

Большинство алгоритмических шаблонов библиотеки, которые работают с данными структуры имеют интерфейсы, которые используют диапазоны. Диапазон - это пара итераторы, которые обозначают начало и конец вычисления. диапазон [i, i) - пустой диапазон; в общем случае диапазон [i, j) относится к в структуре данных, начиная с элемента, на который указывает я и до , но не включая элемент, на который указывает j. Диапазон [i, j) справедливо тогда и только тогда, когда j достижимо из i. Результат применение функций в библиотеке к недопустимым диапазонам undefined.

Таким образом, чтобы ответить на ваши вопросы

Но как насчет следующего примера?

cplusplus.com ошибочен в этом случае

Итак, поведение vector.erase(vector.end(), vector.end()) undefined?

Нет, не срабатывает поведение undefined.

Следовательно, является ли распространенная идиома неправильной?

Нет, это правильно.

Должен ли быть фактический идентификатор удаления/стирания?

Нет необходимости в этом, хотя это тоже хорошо.