Чтение онлайн

ЖАНРЫ

Эффективное использование STL
Шрифт:

Слегка изменим проблему. Вместо того чтобы удалять из с все объекты с заданным значением, давайте удалим все объекты, для которых следующий предикат (совет 39) возвращает

true
:

bool badValue(int х); // Возвращает true для удаляемых объектов

В последовательных контейнерах (

vector
,
string
,
deque
и
list
) достаточно заменить
remove
на
remove_if
:

c.erase(remove_if(c.begin, c.end, badValue), // Лучший способ уничтожения

 c.end); //
объектов, для которых badValue

// возвращает true, в контейнерах

// vector, string и deque

с.remove_if(badValue); // Оптимальный способ уничтожения

// объектов, для которых badValue

// возвращает true, в контейнере

// list

Со стандартными ассоциативными контейнерами дело обстоит посложнее. Существуют два решения: одно проще программируется, другое эффективнее работает. В первом решении нужные значения копируются в новый контейнер функцией

remove_copy
, после чего содержимое двух контейнеров меняется местами:

АссоцКонтейнер<int> с;//с - один из стандартных

… // ассоциативных контейнеров

АссоцКонтейнер<int> goodValues: // Временный контейнер для хранения

// элементов, оставшихся после удаления

remove_copy_if(c.begin, c.end, // Скопировать оставшиеся элементы

 inserter(goodValues, // из с в goodValues

 goodValues.end), badValue);

с.swap(goodValues);// Поменять содержимое с и goodValues

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

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

remove_if
, придется перебирать все элементы с в цикле и принимать решение об удалении текущего элемента.

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

АссоцКонтейнер<int> с;

for(АссоцКонтейнер<int>::iterator i=cbegin; // Наглядный, бесхитростный

i!=cend; // и ошибочный код, который

++i) { // стирает все элементы с

 if (badValue(*i)) c.erase(i); // для которых badValue

} // возвращает true.

// Не поступайте так!

Выполнение этого фрагмента приводит к непредсказуемым результатам. При стирании элемента контейнера все итераторы, указывающие на этот элемент, становятся недействительными. Таким образом, после возврата из

c.erase(i)
итератор
i
становится недействительным. Для нашего цикла это фатально, поскольку после вызова
erase
итератор
i
увеличивается (
++i
в заголовке цикла
for
).

Проблема

решается просто: необходимо позаботиться о том, чтобы итератор переводился на следующий элемент с перед вызовом
erase
. Это проще всего сделать постфиксным увеличением i при вызове:

АссоцКонтейнер<int> с;

for(АссоцКонтейнер<int>::iterator i=c.begin;// Третья часть заголовка

i!=c.end; // цикла for пуста; i теперь

/* пусто */) { // изменяется внутри цикла

if (badValue(*i)) c.erase(i++);// Для удаляемых элементов

 else ++i; // передать erase текущее

} // значение i и увеличить i.

// Для остающихся элементов

// просто увеличить i

Новый вариант вызова

erase
работает, поскольку выражение
i++
равно старому значению
i
, но у него имеется побочный эффект — приращение
i
. Таким образом, мы передаем старое (не увеличенное) значение
i
и увеличиваем
i
перед вызовом
erase
. Именно это нам и требовалось. Хотя это решение выглядит просто, лишь немногие программисты предложат его с первой попытки.

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

badValue
возвращает
true
, мы также хотим регистрировать каждую операцию удаления в журнале.

Для ассоциативных контейнеров задача решается очень просто, поскольку она требует лишь тривиальной модификации созданного цикла:

ofstream logFile;// Файл журнала

АссоцКонтейнер<int> с;

for(АссоцКонтейнер<int>::iterator i=c.begin;// Заголовок цикла остается

i!=c.end;) { // без изменений

 if(badValue(*i)) {

logFile<<"Erasing "<<*i<<'\n'; // Вывод в журнал

c.erase(i++);// Удаление

} else ++i;

}

На этот раз хлопоты возникают с

vector
,
string
и
deque
. Использовать идиому
erase/remove
не удается, поскольку
erase
или
remove_if
нельзя заставить вывести данные в журнал. Более того, вариант с циклом
for
, только что продемонстрированный для ассоциативных контейнеров, тоже не подходит, поскольку для контейнеров
vector
,
string
и
deque
он приведет к непредсказуемым последствиям. Вспомните, что для этих контейнеров в результате вызова
erase
становятся недействительными все итераторы, указывающие на удаляемый элемент. Кроме того, недействительными становятся все итераторы после удаляемого элемента, в нашем примере — все итераторы после
i
. Конструкции вида
i++
,
++i
и т. д. невозможны, поскольку ни один из полученных итераторов не будет действительным.

Поделиться с друзьями: