Правила аннулирования итераторов

543

Каковы правила аннулирования итераторов для контейнеров C ++?

Желательно в формате сводного списка.

(Примечание. Предполагается, что это будет вход в FAQ по C ++ в Stack Overflow . Если вы хотите критиковать идею предоставления FAQ в этой форме, то публикация в meta, с которой все это началось, будет подходящим местом для этого. Этот вопрос отслеживается в чате C ++ , где идея FAQ возникла в первую очередь, поэтому ваш ответ, скорее всего, будет прочитан теми, кто придумал эту идею.)

Гонки легкости на орбите
источник
Должны ли ответы быть в том же формате, что и ваш ответ?
PW
@PW IMO, который был бы предпочтительнее для симметрии, но я не могу навязать это: P
Гонки
что насчет с ++ 20?
Уолтер
1
@Walter Пока не существует;)
Гонки на Легкость на Орбите
Этот вопрос заключается в том, чтобы процитировать Лилу из Футурамы, из дурацких времен, и должен, по моему скромному мнению, оставаться открытым.
Роман Луштрик

Ответы:

112

C ++ 17 (Все ссылки взяты из окончательного рабочего проекта CPP17 - n4659 )


вставка

Контейнеры последовательности

  • vector: Функции insert, emplace_back, emplace, push_backпричина перераспределение , если новый размер больше , чем старые мощности. Перераспределение делает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы в последовательности. Если перераспределение не происходит, все итераторы и ссылки до точки вставки остаются действительными. [26.3.11.5/1]
    Что касается reserveфункции, перераспределение делает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы в последовательности. Никакое перераспределение не должно иметь место во время вставок, которые происходят после вызова до reserve()тех пор, пока вставка не сделает размер вектора больше, чем значение capacity(). [26.3.11.3/6]

  • deque: Вставка в середине deque делает недействительными все итераторы и ссылки на элементы deque. Вставка в любом конце deque делает недействительными все итераторы в deque, но не влияет на достоверность ссылок на элементы deque. [26.3.8.4/1]

  • list: Не влияет на достоверность итераторов и ссылок. Если выброшено исключение, никаких эффектов нет. [26.3.10.4/1]. , , , , , Функции покрыты под этим правилом.
    insertemplace_frontemplace_backemplacepush_frontpush_back

  • forward_list: Ни одна из перегрузок insert_afterне должна влиять на достоверность итераторов и ссылок [26.3.9.5/1]

  • array: Как правило , итераторы массива никогда не признаны недействительными в течение всего срока службы массива. Следует отметить, однако, что во время перестановки итератор будет продолжать указывать на тот же элемент массива и, таким образом, будет изменять его значение.

Ассоциативные Контейнеры

  • All Associative Containers: Элементы insertand emplaceне должны влиять на действительность итераторов и ссылок на контейнер [26.2.6 / 9]

Неупорядоченные ассоциативные контейнеры

  • All Unordered Associative ContainersПерефразирование делает недействительными итераторы, изменяет порядок между элементами и изменения, в которых появляются элементы сегментов, но не делает недействительными указатели или ссылки на элементы. [26.2.7 / 9] и члены не влияют на действительность ссылок элементов контейнера, но могут привести к аннулированию всех итераторов в контейнер. [26.2.7 / 14] и члены не влияют на действительность итераторы , если , где это количество элементов в контейнере до начала операции вставки, является количеством элементов , вставленных, является подсчетом ведра контейнера, и является максимальный коэффициент загрузки контейнера. [26.2.7 / 15]
    insertemplace
    insertemplace(N+n) <= z * BNnBz

  • All Unordered Associative Containers: В случае операции слияния (например, a.merge(a2)) итераторы, ссылающиеся на переданные элементы, и все итераторы, ссылающиеся на, aбудут признаны недействительными, но итераторы для оставшихся элементов a2останутся действительными. (Таблица 91 - Неупорядоченные требования к ассоциативным контейнерам)

Контейнерные адаптеры

  • stack: наследуется от нижележащего контейнера
  • queue: наследуется от нижележащего контейнера
  • priority_queue: наследуется от нижележащего контейнера

подчистка

Контейнеры последовательности

  • vector: Функции eraseи pop_backделают недействительными итераторы и ссылки в или после точки удаления. [26.3.11.5/3]

  • deque: Операция удаления, которая удаляет последний элемент dequeнедействительного элемента, делает недействительным только последний итератор и все итераторы и ссылки на стертые элементы. Операция удаления, которая удаляет первый элемент, dequeно не последний элемент, делает недействительными только итераторы и ссылки на стертые элементы. Операция удаления, которая не удаляет ни первый элемент, ни последний элемент объекта- dequeинвалида, делает недействительным итератор «за концом», а также все итераторы и ссылки на все элементы deque. [Примечание: pop_frontи pop_backоперации удаления. —Конечная записка] [26.3.8.4/4]

  • list: Делает недействительными только итераторы и ссылки на стертые элементы. [26.3.10.4/3]. Это относится и к erase, pop_front, pop_back, clearфункция.
    removeи remove_ifфункции-члены: удаляет все элементы в списке, на которые ссылается итератор списка, iдля которых выполняются следующие условия: *i == value, pred(*i) != false. Делает недействительными только итераторы и ссылки на стертые элементы [26.3.10.5/15].
    uniqueфункция-член - удаляет все элементы, кроме первого, из каждой последовательной группы равных элементов, указанных итератором iв диапазоне, [first + 1, last)для которого *i == *(i-1)(для версии уникального без аргументов) илиpred(*i, *(i - 1))(для версии уникального с аргументом предиката). Делает недействительными только итераторы и ссылки на стертые элементы. [26.3.10.5/19]

  • forward_list: erase_afterаннулирует только итераторы и ссылки на стертые элементы. [26.3.9.5/1].
    removeи remove_ifфункции-члены - удаляет все элементы в списке, на которые ссылается итератор списка i, для которых выполняются следующие условия: *i == value(для remove()), pred(*i)верно (для remove_if()). Делает недействительными только итераторы и ссылки на стертые элементы. [26.3.9.6/12].
    uniqueфункция-член - удаляет все элементы, кроме первого, из каждой последовательной группы равных элементов, упомянутых итератором i в диапазоне [first + 1, last), для которого *i == *(i-1)(для версии без аргументов) илиpred(*i, *(i - 1)) (для версии с предикатом аргумент) имеет место. Делает недействительными только итераторы и ссылки на стертые элементы. [26.3.9.6/16]

  • All Sequence Containers: clearделает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы a, и может сделать недействительным итератор «за конец» (Таблица 87 - Требования к контейнеру последовательности). Но для forward_list, clearне делает недействительными итераторы прошлого конца. [26.3.9.5/32]

  • All Sequence Containers: assignделает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы контейнера. Для vectorи deque, также делает недействительным последний итератор. (Таблица 87 - Требования к контейнеру последовательности)

Ассоциативные Контейнеры

  • All Associative Containers: eraseЧлены должны делать недействительными только итераторы и ссылки на стертые элементы [26.2.6 / 9]

  • All Associative Containers: extractЧлены делают недействительными только итераторы для удаленного элемента; указатели и ссылки на удаленный элемент остаются действительными [26.2.6 / 10]

Контейнерные адаптеры

  • stack: наследуется от нижележащего контейнера
  • queue: наследуется от нижележащего контейнера
  • priority_queue: наследуется от нижележащего контейнера

Общие требования к контейнеру, касающиеся недействительности итератора:

  • Если не указано иное (явным образом или путем определения функции в терминах других функций), вызов функции-члена контейнера или передача контейнера в качестве аргумента библиотечной функции не должны делать недействительными итераторы или изменять значения объектов в этом контейнере. , [26.2.1 / 12]

  • никакая swap()функция не делает недействительными какие-либо ссылки, указатели или итераторы, ссылающиеся на элементы заменяемых контейнеров. [Примечание: итератор end () не ссылается ни на один элемент, поэтому может быть признан недействительным. —Конечная записка] [26.2.1 / (11.6)]

В качестве примеров приведенных выше требований:

  • transformАлгоритм: opи binary_opфункции должны не отменяет итераторы или поддиапазоны, или модифицировать элементы в диапазонах [28.6.4 / 1]

  • accumulateалгоритм: в диапазоне [first, last] binary_opне должен ни изменять элементы, ни делать недействительными итераторы или поддиапазоны [29.8.2 / 1]

  • reduceАлгоритм: binary_op не должен ни делать недействительными итераторы или поддиапазоны, ни модифицировать элементы в диапазоне [first, last]. [29.8.3 / 5]

и так далее...

PW
источник
7
Ой PW ты герой!
Гонки
2
@LightnessRacesinOrbit: пытался сделать это в соответствии с вашим исходным форматом ответа. :)
PW
1
мы можем также иметь список для std::string? Я думаю, что это отличается от std::vectorиз-за SSO
sp2danny
1
@ sp2danny: из-за единого входа stringне выполняется второе общее требование, указанное выше. Так что я не включил это. Также попытался придерживаться того же шаблона предыдущих записей FAQ.
PW
@LightnessRaceswithMonica Спасибо, ребята, за тяжелую работу. У меня есть вопрос, смущающий меня в течение нескольких дней. Что именно означает «недействительный» в этих контекстах? Означает ли это, "invalidated" can mean "no longer points to what it used to", not just "may not point to any valid element"как @Marshall Clow описано в этом ответе ? Или это просто указывает только 1 из 2 условий?
Рик
410

C ++ 03 (Источник: правила недействительности итераторов (C ++ 03) )


вставка

Контейнеры последовательности

  • vector: все итераторы и ссылки до точки вставки не затрагиваются, если только новый размер контейнера не превышает предыдущую емкость (в этом случае все итераторы и ссылки становятся недействительными) [23.2.4.3/1]
  • deque: все итераторы и ссылки становятся недействительными, если только вставленный элемент не находится в конце (передний или задний) deque (в этом случае все итераторы становятся недействительными, но ссылки на элементы не затрагиваются) [23.2.1.3/1]
  • list: все итераторы и ссылки не затронуты [23.2.2.3/1]

Ассоциативные контейнеры

  • [multi]{set,map}: все итераторы и ссылки не затронуты [23.1.2 / 8]

Контейнерные адаптеры

  • stack: наследуется от нижележащего контейнера
  • queue: наследуется от нижележащего контейнера
  • priority_queue: наследуется от нижележащего контейнера

подчистка

Контейнеры последовательности

  • vector: каждый итератор и ссылка после точки стирания становятся недействительными [23.2.4.3/3]
  • deque: все итераторы и ссылки становятся недействительными, если только стертые элементы не находятся в конце (передний или задний) deque (в этом случае недопустимы только итераторы и ссылки на стертые элементы) [23.2.1.3/4]
  • list: только итераторы и ссылки на стертый элемент признаны недействительными [23.2.2.3/3]

Ассоциативные контейнеры

  • [multi]{set,map}: только итераторы и ссылки на стертые элементы становятся недействительными [23.1.2 / 8]

Контейнерные адаптеры

  • stack: наследуется от нижележащего контейнера
  • queue: наследуется от нижележащего контейнера
  • priority_queue: наследуется от нижележащего контейнера

Изменение размера

  • vector: согласно вставке / стиранию [23.2.4.2/6]
  • deque: согласно вставке / стиранию [23.2.1.2/1]
  • list: согласно вставке / стиранию [23.2.2.2/1]

Примечание 1

Если не указано иное (явным образом или путем определения функции в терминах других функций), вызов функции-члена контейнера или передача контейнера в качестве аргумента библиотечной функции не должны делать недействительными итераторы. или изменять значения объектов в этом контейнере. , [23.1 / 11]

Заметка 2

В C ++ 2003 не ясно, подчиняются ли «конечные» итераторы вышеуказанным правилам ; в любом случае вы должны предположить, что они есть (как это имеет место на практике).

Заметка 3

Правила аннулирования указателей такие же, как и правила аннулирования ссылок.

Гонки легкости на орбите
источник
5
Хорошая идея, просто чтобы заметить: я думаю, что ассоциативные контейнеры можно сложить в одну строку, и тогда стоило бы добавить еще одну строку неупорядоченных ассоциативных контейнеров ... хотя я не уверен, как может быть часть перефразирования отображается при вставке / стирании, знаете ли вы способ проверить, будет ли запущена перефразировка или нет?
Матье М.
1
IIRC, где-то в спецификации говорится, что конечный итератор не является итератором "для объектов в этом контейнере". Интересно, как эти гарантии выглядят для конечного итератора в каждом случае?
Йоханнес Шауб - Lit
1
@MuhammadAnnaqeeb: Этот ответ по общему признанию не проясняет, поскольку я взял ярлык, но намерение состоит в том, чтобы сказать, что изменение размера - вставка / стирание, как в случае, если требуется перераспределение, вы можете считать, что это то же самое, что стирание затем заново вставьте все затронутые элементы. Этот раздел ответа, безусловно, может быть улучшен.
Гонки легкости на орбите
1
@Yakk: Но это не так; см. цитируемый стандартный текст. Похоже, это было исправлено в C ++ 11. :)
Гонки Легкости на орбите
1
@metamorphosis: deque хранит данные в несмежных блоках. Вставка в начале или конце может выделить новый блок, но он никогда не перемещается вокруг предыдущих элементов, поэтому указатели остаются действительными. Но правила перехода к следующему / предыдущему элементу изменяются при выделении нового блока, поэтому итераторы становятся недействительными.
Ник Маттео
357

C ++ 11 (Источник: правила недействительности итераторов (C ++ 0x) )


вставка

Контейнеры последовательности

  • vector: все итераторы и ссылки до точки вставки не затрагиваются, если только новый размер контейнера не превышает предыдущую емкость (в этом случае все итераторы и ссылки становятся недействительными) [23.3.6.5/1]
  • deque: все итераторы и ссылки становятся недействительными, если только вставленный элемент не находится в конце (передний или задний) deque (в этом случае все итераторы становятся недействительными, но ссылки на элементы не затрагиваются) [23.3.3.4/1]
  • list: все итераторы и ссылки не затронуты [23.3.5.4/1]
  • forward_list: все итераторы и ссылки не затронуты (применимо к insert_after) [23.3.4.5/1]
  • array: (нет данных)

Ассоциативные контейнеры

  • [multi]{set,map}: все итераторы и ссылки не затронуты [23.2.4 / 9]

Несортированные ассоциативные контейнеры

  • unordered_[multi]{set,map}: все итераторы становятся недействительными, когда происходит перефразировка, но ссылки не затрагиваются [23.2.5 / 8]. Перефразировка не происходит, если вставка не приводит к превышению размера контейнера z * Bгде zмаксимальный коэффициент загрузки иB текущее количество сегментов. [23.2.5 / 14]

Контейнерные адаптеры

  • stack: наследуется от нижележащего контейнера
  • queue: наследуется от нижележащего контейнера
  • priority_queue: наследуется от нижележащего контейнера

подчистка

Контейнеры последовательности

  • vector: каждый итератор и ссылка в или после точки стирания становятся недействительными [23.3.6.5/3]
  • deque: стирание последнего элемента делает недействительными только итераторы и ссылки на стертые элементы и итератор «за конец»; удаление первого элемента делает недействительными только итераторы и ссылки на стертые элементы; Стирание любых других элементов делает недействительными все итераторы и ссылки (включая итератор «за конец») [23.3.3.4/4]
  • list: только итераторы и ссылки на стертый элемент признаны недействительными [23.3.5.4/3]
  • forward_list: только итераторы и ссылки на стертый элемент признаны недействительными (применимо к erase_after) [23.3.4.5/1]
  • array: (нет данных)

Ассоциативные контейнеры

  • [multi]{set,map}: только итераторы и ссылки на стертые элементы становятся недействительными [23.2.4 / 9]

Неупорядоченные ассоциативные контейнеры

  • unordered_[multi]{set,map}: только итераторы и ссылки на стертые элементы становятся недействительными [23.2.5 / 13]

Контейнерные адаптеры

  • stack: наследуется от нижележащего контейнера
  • queue: наследуется от нижележащего контейнера
  • priority_queue: наследуется от нижележащего контейнера

Изменение размера

  • vector: согласно вставке / стиранию [23.3.6.5/12]
  • deque: согласно вставке / стиранию [23.3.3.3/3]
  • list: согласно вставке / стиранию [23.3.5.3/1]
  • forward_list: согласно вставке / стиранию [23.3.4.5/25]
  • array: (нет данных)

Примечание 1

Если не указано иное (явным образом или путем определения функции в терминах других функций), вызов функции-члена контейнера или передача контейнера в качестве аргумента библиотечной функции не должны делать недействительными итераторы или изменять значения объектов в этом контейнере. , [23.2.1 / 11]

Заметка 2

функция swap () не делает недействительными какие-либо ссылки, указатели или итераторы, ссылающиеся на элементы переставляемых контейнеров. [Примечание: итератор end () не ссылается ни на один элемент, поэтому может быть признан недействительным . —Конечная записка] [23.2.1 / 10]

Заметка 3

Кроме вышеупомянутого предостережение в отношении swap(), не ясно , «конец» итераторы ли при условии соблюдения перечисленных выше для каждого контейнера правил ; в любом случае вы должны предположить, что они есть.

Примечание 4

vectorи все неупорядоченный ассоциативно контейнеры поддержки , reserve(n)которая гарантирует , что никакого автоматического изменения размера не будет происходить , по крайней мере до тех пор , размер контейнера не растет n. Следует проявлять осторожность с неупорядоченными ассоциативными контейнерами, поскольку в будущем предложении будет указываться минимальный коэффициент загрузки, который позволил бы произвести перефразирование insertпосле достаточного количества eraseопераций, уменьшив размер контейнера ниже минимального; гарантия должна считаться потенциально недействительной после erase.

Гонки легкости на орбите
источник
Кроме того swap(), каковы правила валидации итератора при назначении копирования / перемещения?
Goodbyeera
@LightnessRacesinOrbit: Как и вставка, стирание, изменение размера и свопирование, назначение копирования / перемещения также являются функциями-членами std :: vector, поэтому я думаю, что вы могли бы также предоставить им правила валидации итераторов.
Goodbyeera
@goodbyeera: Вы имеете в виду копирование / перемещение, присваивая элемент? Это не повлияет на итераторы. С чего бы это? Вы попали в Примечание 1 выше.
Гонки легкости на орбите
1
Я думаю, что допустил ошибку, потому std::basic_stringчто, кажется, не считается контейнером и, конечно, не контейнером в разделе стандарта, к которому относится примечание. Тем не менее, где говорится, что SSO запрещен (я знаю, COW)?
Дедупликатор
2
Являются ли эти правила одинаковыми в C ++ 14? C ++ 17 (насколько известно сейчас)?
einpoklum
40

Вероятно, стоит добавить, что итератор вставки любого вида ( std::back_insert_iterator,std::front_insert_iterator , std::insert_iterator) гарантированно остается в силе до тех пор , как все вставки выполняются с помощью этого итератора и не происходит никакого другого независимый итератор-недействительность события.

Например, когда вы выполняете серию операций вставки в std::vector помощьюstd::insert_iterator вполне возможно, что эти вставки вызовут перераспределение вектора, что сделает недействительными все итераторы, которые «указывают» на этот вектор. Тем не менее, рассматриваемый итератор вставки гарантированно останется действительным, т.е. вы можете безопасно продолжить последовательность вставок. Нет необходимости беспокоиться о перераспределении векторов вообще.

Это, опять же, относится только к вставкам, выполняемым через сам итератор вставки. Если событие отмены итератора инициируется каким-либо независимым действием над контейнером, то итератор вставки также становится недействительным в соответствии с общими правилами.

Например, этот код

std::vector<int> v(10);
std::vector<int>::iterator it = v.begin() + 5;
std::insert_iterator<std::vector<int> > it_ins(v, it);

for (unsigned n = 20; n > 0; --n)
  *it_ins++ = rand();

гарантированно выполнить правильную последовательность вставок в вектор, даже если вектор «решает» перераспределить где-то в середине этого процесса. Итератор it, очевидно, станет недействительным, но it_insостанется действительным.

Муравей
источник
22

Поскольку этот вопрос набирает столько голосов и становится чем-то вроде FAQ, я думаю, что было бы лучше написать отдельный ответ, чтобы упомянуть одно существенное различие между C ++ 03 и C ++ 11 в отношении влияния std::vectorоперации вставки на действительность итераторов и ссылок относительно reserve()и capacity(), на которые большинство голосовавших не обратили внимания.

C ++ 03:

Перераспределение делает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы в последовательности. Гарантируется, что перераспределение не происходит во время вставок, которые происходят после вызова функции Reserve (), до тех пор, пока вставка не сделает размер вектора больше размера, указанного в последнем вызове Reserve (). .

C ++ 11:

Перераспределение делает недействительными все ссылки, указатели и итераторы, ссылающиеся на элементы в последовательности. Гарантируется, что перераспределение не происходит во время вставок, которые происходят после вызова функции Reserve (), до тех пор, пока вставка не сделает размер вектора больше, чем значение Capacity () .

Таким образом, в C ++ 03 это не " unless the new container size is greater than the previous capacity (in which case all iterators and references are invalidated)", как упомянуто в другом ответе, вместо этого это должно быть " greater than the size specified in the most recent call to reserve()". Это одна вещь, которая отличает C ++ 03 от C ++ 11. В C ++ 03, когда a insert()приводит к тому, что размер вектора достигает значения, указанного в предыдущем reserve()вызове (который может быть меньше текущего, capacity()поскольку a reserve()может привести к большему, capacity()чем запрашиваемый), любое последующее insert()может привести к перераспределению и аннулированию все итераторы и ссылки. В C ++ 11 этого не произойдет, и вы всегда можете доверятьcapacity() с уверенностью знать, что следующее перераспределение не произойдет до того, как размер превысит capacity().

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

neverhoodboy
источник
14
Однако я бы застрелил любого компилятора, который сделал это со мной, и никакое жюри в стране не осудило бы меня.
Якк - Адам Невраумонт
9
Я не «не заметил» этого; это была редакционная ошибка в C ++ 03, которая была исправлена ​​в C ++ 11. Ни один основной компилятор не воспользуется этой ошибкой.
Гонки легкости на орбите
1
@Yakk Я думаю, что gcc уже делает недействительными итераторы в таких ситуациях.
ShreevatsaR
2

Вот хорошая сводная таблица от cppreference.com :

введите описание изображения здесь

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

DarioP
источник