Правила аннулирования итератора специфичны для контейнера.
Теперь инвалидация может иметь 2 значения с вектором:
- Недействительность = точка за пределами диапазона, определенного [begin,end]
- Недействительность = указать на объект, отличный от исходного
Как видите, второй гораздо строже:
std::vector<int> myVector;
myVector.push_back(0);
myVector.push_back(1);
std::vector<int>::iterator it = myVector.begin(); // it points to 0
myVector.erase(it); // it points to 1
myVector.erase(it); // it == myVector.end()
В этом случае он «действителен» в том смысле, что он всегда находится в инклюзивном диапазоне [begin, end] и поэтому может безопасно использоваться для любой операции с myVector. С другой стороны, выражение (*it) продолжает меняться: сначала оно возвращает 0, затем 1, затем имеет неопределенное поведение...
В общем, люди скорее будут говорить о втором требовании, и отмена итератора просто означает, что (*он) может не дать такой же результат, как раньше.
Теперь, когда это сказано, есть несколько способов аннулировать итератор в векторе (фактически это менее стабильная структура STL).
При добавлении элементов:
- Это может вызвать перераспределение (1), если myVector.size() == myVector.capacity(), поскольку проверка этого подвержена ошибкам, мы обычно считаем, что любое добавление сделает недействительными итераторы
- Если вы хотите быть «придирчивым» и знаете, что перераспределение не срабатывает, вам все равно придется беспокоиться о
insert
. Вставка элемента делает недействительными итераторы, указывающие на эту текущую позицию и все последующие, поскольку элементы сдвигаются на один шаг к концу вектора.
При удалении элементов:
- Перераспределения нет, даже если буфер теперь намного больше, чем нужно. Однако это можно сделать принудительно, используя идиому сжимать по размеру (2).
- Все итераторы, указывающие на удаленный элемент, становятся недействительными. В частности, предыдущий «конечный» итератор теперь находится за пределами диапазона [begin, end] и не может безопасно использоваться, например, в алгоритмах STL.
(1) Внутренняя структура std::vector представляет собой массив T, это связано с совместимостью с C-программами (с использованием &myVector.front() в качестве адреса массива) и потому, что это гарантирует непрерывность и минимум пространство накладных расходов (т. е. объем пространства, занимаемый собственными данными вектора, по сравнению с объемом пространства, занимаемым объектом)
В любой момент вы можете узнать, сколько объектов может содержать вектор, используя метод .capacity().
Когда вы хотите вставить объект, а вектор не имеет необходимой емкости, срабатывает вызов метода .reserve(size_t). Этот метод, если количество требуемых элементов превышает текущую емкость, инициирует перераспределение.
Затем вектор выделяет новый массив элементов (его размер обычно равен 2*n+1, где n — текущая емкость), копирует элементы текущего массива в новый массив, отбрасывает текущий массив.
Поскольку он отбрасывает текущий массив, ваши итераторы становятся недействительными, поскольку векторные итераторы обычно представляют собой простые указатели (для эффективности).
Обратите внимание, что если бы итераторы были реализованы как: ссылка на вектор + счетчик, а разыменование было бы на самом деле *(&m_vector.front() + n), перераспределение не сделало бы их недействительными... но они были бы менее эффективными.
(2) Уменьшить размер: предупреждение, это вызывает КОПИЮ элементов и делает недействительными итераторы.
// myVector has 10 elements, but myVector.capacity() == 1000
myVector.swap(std::vector<int>(myVector));
Сначала он создает временный вектор, который будет выделять столько памяти, сколько необходимо (минимум зависит от библиотеки), и копирует элементы myVector. Затем операция подкачки обменивает буферы из myVector и этой копии, и, таким образом, myVector теперь содержит буфер с минимально необходимым объемом памяти. В конце операции временная память уничтожается, а память, которую она содержит, освобождается.
person
Matthieu M.
schedule
26.10.2009