Почему vector :: pop_back делает недействительным итератор (end () — 1)?

Примечание: вопрос относится к erase, тоже. Смотри снизу.


В чем причина того, что end() - 1 итератор становится недействительным после pop_back называется на vector?

Чтобы уточнить, я имею в виду эту ситуацию:

std::vector<int> v;
v.push_back(1);
v.push_back(2);

std::vector<int>::iterator i1 = v.begin(), i2 = v.end() - 1, i3 = v.begin() + 1;

v.pop_back();

// i1 is still valid
// i2 is now invalid
// i3 is now invalid too

std::vector<int>::iterator i4 = v.end();

assert(i2 == i4);  // undefined behavior (but why should it be?!)
assert(i3 == i4);  // undefined behavior (but why should it be?!)

Почему это происходит? (то есть когда эта недействительность когда-нибудь окажется полезной для реализации?)

(Обратите внимание, что это не просто теоретическая проблема. Visual C ++ 2013 — и, вероятно, также 2012 — отображать ошибку, если вы пытаетесь сделать это в режиме отладки, если у вас есть _ITERATOR_DEBUG_LEVEL установлен в 2.)


относительно erase:

Обратите внимание, что тот же вопрос относится к erase:
Почему erase(end() - 1, end()) аннулировать end() - 1?

(Поэтому, пожалуйста, не говорите, «pop_back аннулирует end() - 1 потому что это эквивалентно звонку erase(end() - 1, end())«; вот только напрашиваюсь.)

6

Решение

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

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

Поддержка этой линии рассуждений происходит из предложений об аннулировании итераторов других операций в контейнере. Например, на insertстандарт гарантирует, что при отсутствии перераспределения итераторы до точка вставки остается в силе. Exceptio probat regulam в casibus non excisis, это делает недействительными все итераторы после точка вставки.

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

Если мы рассмотрим правильность итератора как срок действия указателя, тогда ни один из итераторов в вектор не будет признан недействительным во время erase операция. end()-1 Итератор станет не разыменованным, но он может остаться в силе, что не так.

2

Другие решения

pop_back обычно определяется с точки зрения erase(end()-1, end()),

Когда вы удаляете диапазон итераторов из вектора, все итераторы с первого стирания и вперед стираются. Это включает в себя диапазон один.

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

Оба приведенных выше правила должны быть изменены, чтобы получить желаемое поведение. От первого к чему-то вроде «если вы не удалите последний элемент при этом, в этом случае первый стертый элемент становится итератором« один за другим »». И все еще действительный итератор станет не разыменованным, возможно, уникальным изменением состояния для итератора, который, насколько я знаю, не имеет прецедента в C ++.

Стоимость в стандарте — как дополнительная хитрость, чтобы покрыть запрошенное поведение, так и проверки работоспособности для строгих итераторов. Преимущество — ну, я не вижу одного: в каждой ситуации вам нужно будет точно знать, что только что произошло (очень конкретный итератор просто стал одним концом вместо того, чтобы быть признанным недействительным), и если вы знаете, что это так, вы мог бы просто поговорить о end,

И слова обязательны. Когда вы звоните erase( a, b )каждый итератор из a на его состояние будет каким-то образом изменено (*a не будет возвращать то же значение, и как эти изменения должны быть указаны). C ++ выбирает легкий путь и просто утверждает, что каждый итератор, состояние которого изменяется erase становится недействительным, и его использование является неопределенным поведением: это позволяет максимальную широту реализатору.

Теоретически, это также может позволить оптимизацию. Если вы разыменовываете итератор до и после erase операции, значение, которое вы получаете, можно считать одинаковым! (при условии невозможности косвенной модификации контейнера внутри деструкторов объекта, что также может быть доказано).

0

По вопросам рекламы [email protected]