Стереть элементы из мультинабора STL с помощью итератора

Я поддерживаю набор итераторов мультинаборного контейнера в отдельной структуре данных. Через некоторое время я выбираю один итератор из этой структуры данных, а затем удаляю связанный с ним элемент из мультимножества. Сначала я использую это что-то вроде этого:

#include <iostream>
#include <set>

int main ()
{
  std::multiset<int> myints;
  std::cout << "0. size: " << myints.size() << '\n';

  for (int i=0; i<10; i++) myints.insert(i);
  std::cout << "1. size: " << myints.size() << '\n';

  myints.insert (5);
  std::cout << "2. size: " << myints.size() << '\n';

  std::multiset<int>::iterator it = myints.find(5);
  myints.erase (it);
  std::cout << "3. size: " << myints.size() << '\n';
  myints.erase (it);
  std::cout << "4. size: " << myints.size() << '\n';
  return 0;
}

Однако оказывается, что второй myints.erase (it); вызывает ошибку сегментации. Поэтому я перехожу к следующему коду, и он работает. Мне было интересно, хороший ли это путь или это рабочая undefined ситуация:

int main ()
{
  std::multiset<int> myints;
  std::cout << "0. size: " << myints.size() << '\n';

  for (int i=0; i<10; i++) myints.insert(i);
  std::cout << "1. size: " << myints.size() << '\n';

  myints.insert (5);
  std::cout << "2. size: " << myints.size() << '\n';

  std::multiset<int>::iterator it = myints.find(5);
  myints.erase (it);
  std::cout << "3. size: " << myints.size() << '\n';

  std::multiset<int>::iterator newit = myints.find(*it);
  myints.erase (newit);
  std::cout << "4. size: " << myints.size() << '\n';

  return 0;
}

person ARH    schedule 13.02.2013    source источник
comment
Оба подхода имеют ошибки - можете ли вы более четко объяснить, что вы на самом деле пытаетесь сделать?   -  person us2012    schedule 13.02.2013
comment
Я создаю отсортированный список, используя мультисетевой контейнер. Следовательно, мне нужно стереть некоторые элементы из середины отсортированного списка, не зная их ключей. Например, отсортированный список 1 2 3 4. Через некоторое время мне нужно удалить один элемент из отсортированного списка (например, средний элемент), не зная его ключа. Поэтому я поддерживаю набор итераторов, указывающих на каждый элемент отсортированного списка в отдельной структуре данных, позже я выберу один итератор и удалю связанные элементы из отсортированного списка.   -  person ARH    schedule 13.02.2013
comment
Да, но какие из них вам нужно удалить? Каково условие удаления элемента?   -  person us2012    schedule 13.02.2013
comment
Если вы не знаете ключ, как удалить элемент? а как узнать что удаляешь?   -  person billz    schedule 13.02.2013
comment
После любой операции вставки или стирания любой итератор, находящийся во внешней структуре данных, может стать недействительным. Вы не можете этого сделать.   -  person jmucchiello    schedule 13.02.2013


Ответы (2)


erase(it) делает недействительным итератор it, т. е. он бесполезен после erase, и любые действия с ним приводят к неопределенному поведению. (Вы, вероятно, ожидали, что он «перейдет к следующему элементу», когда элемент, на который он указывает, будет стерт, но это не то, что он делает.)

Ваш второй подход не исправляет это. Это может сработать случайно, но вы все еще повторно используете it после того, как стерли его.


Изменить: Учитывая ваше описание «Я хочу стереть только один 5 из мультимножества и сохранить его действительным после стирания для следующего стирания», вы можете сделать это, создав копию итератора, увеличив оригинал а затем стирание через копию:

it = myints.find(5);
// better add a check here to make sure there actually is a 5 ...
std::multiset<int>::iterator newit = it;
it++;
myints.erase(newit);

Поскольку вы уже увеличили it, он остается действительным, поскольку не указывает на элемент, уничтоженный erase.

Однако я, честно говоря, не могу представить ситуацию, в которой это могло бы быть действительно полезно, а точнее, необходимо.

person us2012    schedule 13.02.2013
comment
Есть ли способ сохранить его живым (указав на следующий элемент с тем же ключом) после стирания (его)? - person ARH; 13.02.2013
comment
Может не быть следующего элемента с «тем же ключом» (я думаю, вы просто имеете в виду эквивалентный следующий элемент - это мультимножество, а не мультимап). Что вы на самом деле пытаетесь сделать? Стереть все пятерки с мультисета? - person us2012; 13.02.2013
comment
Я хочу стереть только один 5 из мультисета и сохранить его действительным после стирания для следующего стирания. - person ARH; 13.02.2013
comment
@ARH Действительно для чего? Вы только что удалили элемент, на который он ссылается. - person jmucchiello; 13.02.2013
comment
@jmucchiello Учитывая первый комментарий к моему ответу, я предполагаю, что он имеет в виду указание на элемент сразу после стертого, хотя я сомневаюсь в практичности этого для отсортированной структуры, такой как multiset. - person us2012; 13.02.2013
comment
@ us2012 Да, но, учитывая расплывчатость этих требований, я не думаю, что он продумал, чего он действительно хочет. И мой вопрос пытается заставить его задуматься об этом. - person jmucchiello; 13.02.2013
comment
Разве вы не можете просто написать myints.erase(it++); вместо использования временной переменной (newit)? - person Kira; 11.11.2015

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

Вы можете исправить первый случай, внеся следующие изменения в свой код. Оператор постинкремента вернет новый объект и переместит итератор в следующую позицию. Я также предлагаю выполнить окончательную проверку перед стиранием, иначе вы можете получить неопределенное поведение.

      std::multiset<int>::iterator it = myints.find(5);
      if(it != myints.end())
      myints.erase (it++);
      std::cout << "3. size: " << myints.size() << '\n';
      if(it != myints.end())
      myints.erase (it++);
      std::cout << "4. size: " << myints.size() << '\n';
person user258367    schedule 14.02.2013