// ошибка: поведение этого цикла непредсказуемо

auto begin = v.begin(),

 end = v.end(); // плохая идея хранить значение итератора end

while (begin != end) {

 // некоторые действия

 // вставить новое значение и переприсвоить итератор begin, который

 // в противном случае окажется недопустимым

 ++begin; // переместить begin, поскольку вставка необходима после

          // этого элемента

 begin = v.insert(begin, 42); // вставить новое значение

 ++begin; // переместить begin за только что добавленный элемент

}

Поведение этого кода непредсказуемо. На многих реализациях получится бесконечный цикл. Проблема в том, что возвращенное функцией end() значение хранится в локальной переменной end. В теле цикла добавляется элемент. Добавление элемента делает итератор, хранимый в переменной end, недопустимым. Этот итератор не указывает ни на какой элемент в контейнере v, ни на следующий после его конца.

Не кешируйте возвращаемый функцией end() итератор в циклах, которые вставляют или удаляют элементы в контейнере deque, string или vector.

Вместо того чтобы хранить итератор end, его следует повторно вычислять после каждой вставки:

// существенно безопасный: повторно вычислять end после каждого

// добавления/удаления элементов

while (begin != v.end()) {

 // некоторые действия

 ++begin; // переместить begin, поскольку вставка необходима после

          // этого элемента

 begin = v.insert(begin, 42); // вставить новое значение

 ++begin; // переместить begin за только что добавленный элемент

}

Упражнения раздела 9.3.6

Упражнение 9.31. Программа из пункта «Создание циклов, которые изменяют контейнер», удаляющая четные и дублирующая нечетные элементы, не будет работать с контейнером list или forward_list. Почему? Переделайте программу так, чтобы она работала и с этими типами тоже.

Упражнение 9.32. Будет ли допустим в указанной выше программе следующий вызов функции insert()? Если нет, то почему?

iter = vi.insert(iter, *iter++);

Упражнение 9.33. Что будет, если в последнем примере этого раздела не присваивать переменной begin результат вызова функции insert()? Напишите программу без этого присвоения, чтобы убедиться в правильности своего предположения.

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

iter = vi.begin();

while (iter != vi.end())

 if (*iter % 2)

  iter = vi.insert(iter, *iter);

 ++iter;

<p><image l:href="#reader.png"/>9.4. Как увеличивается размер вектора</p>

Для обеспечения быстрого произвольного доступа элементы вектора хранятся последовательно — каждый элемент рядом с предыдущим. Как правило, нас не должно заботить то, как реализован библиотечный тип; достаточно знать, как его использовать. Но в случае векторов и строк реализация частично просачивается в интерфейс.

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

Перейти на страницу:

Похожие книги