Правила недійсності ітератора


543

Які правила відключення ітератора для контейнерів C ++?

Переважно у форматі підсумкового списку.

(Примітка. Це означає, що це запис до C ++ FAQ Stack Overflow . Якщо ви хочете критикувати ідею надання поширених запитань у цій формі, то тут слід зробити публікацію про мета, яка почала все це . Відповіді на це питання відстежується в кімнаті для спілкування на C ++ , де ідея поширених запитань почалася в першу чергу, тому велику ймовірність отримати відповідь ті, хто придумав цю ідею.)


Чи повинні відповіді бути у тому ж форматі, що і ваша відповідь?
PW

@PW IMO, який би віддав перевагу симетрії, але я не можу цього застосувати: P
Гонки легкості в орбіті

що з c ++ 20?
Вальтер

1
@Walter Ще не існує;)
Гонки легкості в орбіті

Це питання, цитувати Леелу з Футурами, з дурних віків, і, на мою скромну думку, слід залишати відкритим.
Роман Луштрик

Відповіді:


110

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: Вставка в середині дека приводить до недійсності всіх ітераторів і посилань на елементи дека. Вставка на будь-якому кінці деке недійсна для всіх ітераторів дека, але не впливає на обгрунтованість посилань на елементи дека. [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: insertІ emplaceчлени не впливають на дійсність ітератори і посилання на контейнер [26.2.6 / 9]

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

  • All Unordered Associative Containers: Повторна перевірка визнає недійсним ітератори, змінює впорядкування між елементами та змінює, у яких елементах відра відображаються, але не приводить до недійсності покажчиків чи посилань на елементи. [26.2.7 / 9]
    Члени insertта emplaceчлени не повинні впливати на дійсність посилань на елементи контейнера, але можуть визнати недійсними всі ітератори контейнера. [26.2.7 / 14]
    Члени insertі emplaceчлени не повинні впливати на дійсність ітераторів, якщо (N+n) <= z * B, де Nкількість елементів у контейнері до операції вставки, nкількість вставлених елементів, Bце кількість відра контейнера і zце максимальний коефіцієнт завантаження контейнера [26.2.7 / 15]

  • 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)для якого (для версії унікального з аргументом предиката). Недійсні лише ітератори та посилання на стирані елементи. [26.3.10.5/19]*i == *(i-1) (для унікальної версії без аргументів) абоpred(*i, *(i - 1))

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

  • All Sequence Containers: clearвизнає недійсним всі посилання, покажчики та ітератори, що посилаються на елементи а та можуть визнати недійсним ітератор минулого кінця (Таблиця 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алгоритм: У діапазоні [перший, останній] binary_opне слід змінювати елементи, ані недійсні ітератори чи підрядки [29.8.2 / 1]

  • reduceалгоритм: binary_op не повинен анулювати ітераторів чи підрядів, а також не змінювати елементи в діапазоні [перший, останній]. [29.8.3 / 5]

і так далі...



2
@LightnessRacesinOrbit: Намагався зробити це відповідно до оригінального формату відповідей. :)
PW

1
ми можемо також мати список std::string? Я думаю, що це відрізняється від std::vectorSSO
sp2danny

1
@ sp2danny: Через SSO 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: всі ітератори та посилання недійсні, за винятком випадків, коли вставлений член знаходиться в кінці (спереду чи ззаду) деке (у цьому випадку всі ітератори недійсні, але посилання на елементи не впливають) [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: всі ітератори та посилання недійсні, за винятком випадків, коли стирані члени знаходяться в кінці (спереду або ззаду) деке (у цьому випадку лише ітератори та посилання на стирані члени недійсні) [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, десь специфікація говорить про те, що ітератор кінця не є ітератором "для об'єктів всередині цього контейнера". Цікаво, як ці гарантії шукають ітератор кінця в кожному випадку?
Йоханнес Шауб - ліб

1
@MuhammadAnnaqeeb: Ця відповідь, безумовно, не дає зрозуміти, оскільки я скористався ярликом, але наміром є сказати, що зміна розміру - це вставлення / стирання, як якщо потрібно перерозподіл, ви можете вважати, що це те саме, що стирання потім повторно вставити всі уражені елементи. Цей розділ відповіді, безумовно, можна було б покращити.
Гонки легкості на орбіті

1
@Yakk: Але це не так; див. цитований стандартний текст. Схоже, це було виправлено в C ++ 11, хоча. :)
Гонки легкості по орбіті

1
@metamorphosis: deque зберігає дані в безперервних блоках. Вставлення на початку або в кінці може виділити новий блок, але він ніколи не рухається навколо попередніх елементів, тому покажчики залишаються дійсними. Але правила переходу до наступного / попереднього елемента змінюються, якщо виділяється новий блок, тому ітератори недійсні.
Нік Маттео

357

C ++ 11 (Джерело: Правила інвалідизації ітератора (C ++ 0x) )


Введення

Послідовні контейнери

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

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

  • [multi]{set,map}: всі ітератори та посилання не зачіпаються [23.2.4 / 9]

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

  • unordered_[multi]{set,map}: всі ітератори недійсні, коли відбувається повторна повторна перевірка, але посилання не зачіпаються [23.2.5 / 8]. Rehashing не виникає , якщо вставка не викликає розмір контейнера, щоб перевищити , 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: (n / a)

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

  • [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: (n / a)

Примітка 1

Якщо не вказано інше (явно або шляхом визначення функції з точки зору інших функцій), виклик функції члена контейнера або передача контейнера в якості аргументу функції бібліотеки не повинно визнати недійсними ітератори об'єктів у цьому контейнері або змінити їх значення. . [23.2.1 / 11]

Примітка 2

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

Примітка 3

Крім вищезазначеного застереження щодо swap(), не ясно, чи підпадають ітератори на "кінець" вищезазначених правил щодо контейнерів ; ви повинні припустити, все одно, що вони є.

Примітка 4

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


Крім того swap(), які правила щодо дійсності ітератора при призначенні копії / переміщення?
побачення

@LightnessRacesinOrbit: Як і вставлення, стирання, зміна розміру та підміна, призначення копіювання / переміщення також є функціями-членами std :: vector, тому я думаю, ви могли б також надати правила дій ітератора для них.
побачення

@goodbyeera: Ви маєте на увазі копіювати / переміщувати, призначаючи елемент? Це не вплине на жодних ітераторів. Навіщо це? Ви звертаєтесь до Примітки 1 вище.
Гонки легкості на орбіті

1
Я думаю, що я допустив помилку, оскільки std::basic_stringвін, здається, не вважається контейнером, і, звичайно, не є контейнером у розділі стандарту, до якого застосовується примітка. І все-таки, де сказано, що SSO заборонено (я знаю, що це КРВ)?
Дедуплікатор

2
Чи однакові ці правила в C ++ 14? C ++ 17 (наскільки зараз відомо)?
einpoklum

40

Це, ймовірно , варто додати , що вставка итератор будь-якого виду ( std::back_insert_iterator, std::front_insert_iterator, std::insert_iterator) гарантовано залишається в силі до тих пір , як все вставки виконуються з допомогою цього ітератора і не відбувається ніякого іншого незалежний итератор-недійсність події.

Наприклад, коли ви виконуєте серію операцій з введенням в a 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:

Перерозподіл скасовує недійсні всі посилання, покажчики та ітератори, що посилаються на елементи в послідовності. Гарантується, що не відбувається перерозподілу під час вставки, яка відбувається після виклику резерву () до моменту, коли вставка зробить розмір вектора більшим за розмір, зазначений в останньому виклику до резерву () .

C ++ 11:

Перерозподіл скасовує недійсні всі посилання, покажчики та ітератори, що посилаються на елементи в послідовності. Гарантується, що не відбувається перерозподілу під час вставки, що відбувається після виклику резерву () до моменту, коли вставка зробить розмір вектора більшим за значення ємності () .

Тож у 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, як тільки insert()причина призводить до того, що розмір вектора досягає значення, зазначеного в попередньому reserve()виклику (яке цілком може бути меншим за поточний, capacity()оскільки a reserve()може призвести до більшого, capacity()ніж просили), будь-який наступний insert()може призвести до перерозподілу та визнання недійсним всі ітератори та посилання. У C ++ 11 цього не відбудеться, і ви завжди можете довіряти, capacity()що з впевненістю дізнаєтесь, що наступне перерозподіл не відбудеться до того, як розмір перевищить capacity().

На закінчення, якщо ви працюєте з вектором C ++ 03 і хочете переконатися, що перерозподіл не відбудеться під час вставки, це значення аргументу, який ви передали раніше, щоб reserve()ви повинні перевірити розмір, а не повернене значення дзвінка capacity(), інакше ви можете здивуватися " передчасному " перерозподілу.


14
Однак я би знімав будь-якого уповноваженого, який мені це зробив, і жодне присяжне в цій землі не засудило б мене.
Якк - Адам Невраумон

9
Я цього "не помітив"; це була виправлена ​​помилка в редакторі C ++ 03, виправлена ​​в C ++ 11. Жоден основний компілятор не використовує помилку.
Гонки легкості по орбіті

1
@Yakk Я думаю, що gcc вже скасовує ітератори в таких ситуаціях.
ШреєвацаР

2

Ось приємна підсумкова таблиця від cppreference.com :

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

Тут вставка стосується будь-якого способу, який додає один або більше елементів до контейнера, а стирання стосується будь-якого способу, який видаляє один або більше елементів з контейнера.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.