это происходит относительно быстро. В проведенных мною простейших тестах выигрыш по скорости достигал 40%, хотя в вашем случае цифры могут быть другими. Не удивляйтесь, если быстродействие будет расти со временем; итераторы
istreambuf_iterator
населяют один из заброшенных уголков STL, и авторы реализаций еще недостаточно позаботились об их оптимизации. Например, в моих примитивных тестах итераторы
istreambuf_iterator
одной из реализаций работали всего на 5% быстрее, чем
istream_iterator
. В таких реализациях остается широкий простор для оптимизации
istreambuf_iterator
.
Если вы планируете читать из потока
по одному символу, не нуждаетесь в средствах форматирования ввода и следите за эффективностью выполняемых операций, три лишних символа на итератор — не такая уж дорогая цена за заметный рост быстродействия. При неформатном посимвольном вводе всегда рассматривайте возможность применения
sreambuf_iterator
.
Раз уж речь зашла о буферизованных итераторах, следует упомянуть и об использовании
ostreambuf_iterator
при неформатном посимвольном выводе. По сравнению с
ostream_iterator
итераторы
ostreambuf_iterator
обладают меньшими затратами (при меньших возможностях), поэтому обычно они превосходят их по эффективности.
Алгоритмы
В начале главы 1 я упоминал о том, что львиная доля репутации STL связана с контейнерами, и это вполне объяснимо. Контейнеры обладают массой достоинств и упрощают повседневную работу бесчисленных программистов C++. Но и алгоритмы STL тоже по-своему замечательны и в той же степени облегчают бремя разработчика. Существует более 100 алгоритмов, и встречается мнение, что они предо-ставляют программисту более гибкий инструментарий по сравнению с контейнерами (которых всего-то восемь!). Возможно, недостаточное применение алгоритмов отчасти и объясняется их количеством. Разобраться в восьми типах контейнеров проще, чем запомнить имена и предназначение многочисленных алгоритмов.
В этой главе я постараюсь решить две основные задачи. Во-первых, я представлю некоторые малоизвестные алгоритмы и покажу, как с их помощью упростить себе жизнь. Не беспокойтесь, вам не придется запоминать длинные списки имен. Алгоритмы, представленные в этой главе, предназначены для решения повседневных задач — сравнение строк без учета регистра символов, эффективный поиск
n
объектов, в наибольшей степени соответствующих заданному критерию, обобщение характеристик всех объектов в заданном интервале и имитация
copy_if
(алгоритм из исходной реализации HP STL, исключенный в процессе стандартизации).
Во-вторых, я научу вас избегать стандартных ошибок, возникающих при работе с алгоритмами. Например, при вызове алгоритма remove и его родственников
remove_if
и
unique
необходимо точно знать, что эти алгоритмы делают (и чего они не делают). Данное правило особенно актуально при вызове
remove
для интервала, содержащего указатели. Многие алгоритмы работают только с отсортированными интервалами, и программист должен понимать, что это за алгоритмы и почему для них установлено подобное ограничение. Наконец, одна из наиболее распространенных ошибок, допускаемых при работе с алгоритмами, заключается в том, что программист предлагает алгоритму записать результаты своей работы в несуществующую область памяти. Я покажу, как это происходит и как предотвратить эту ошибку.
Возможно, к концу главы вы и не будете относиться к алгоритмам с тем же энтузиазмом, с которым обычно относятся к контейнерам, но по крайней мере будете чаще применять их в своей работе.
Совет 30. Следите за тем, чтобы приемный интервал имел достаточный размер
Контейнеры STL автоматически увеличиваются с добавлением новых объектов (функциями
insert
,
push_front
,
push_back
и т. д.). Автоматическое изменение размеров чрезвычайно удобно, и у многих программистов создается ложное впечатление, что контейнер сам обо всем позаботится и им никогда не
придется следить за наличием свободного места. Если бы так!
Проблемы возникают в ситуации, когда программист думает о вставке объектов в контейнер, но не сообщает о своих мыслях STL. Типичный пример:
int transmogrify(int х); // Функция вычисляет некое новое значение
// по переданному параметру х
vector<int> values;
… // Заполнение вектора values данными
vector<int> results; // Применить transmogrify к каждому объекту
transform(values.begin, // вектора values и присоединить возвращаемые
values.end, // значения к results.
results.end, // Фрагмент содержит ошибку!
transmogrify);
В приведенном примере алгоритм
transform
получает информацию о том, что приемный интервал начинается с
results.end
. С этой позиции он и начинает вывод значений, полученных в результате вызова
transmogrify
для каждого элемента
values
. Как и все алгоритмы, использующие приемный интервал,
transform
записывает свои результаты, присваивая значения элементам заданного интервала. Таким образом,
transform
вызовет
transmogrify
для
values[0]
и присвоит результат
*results.end
. Затем функция
transmogrify
вызывается для
values[1]
с присваиванием результата
*(results.end+1)
. Происходит катастрофа, поскольку в позиции
*results.end
(и тем более в
*(results.end+1))
не существует объекта! Вызов
transform
некорректен из-за попытки присвоить значение несуществующему объекту (в совете 50 объясняется, как отладочная реализация STL позволит обнаружить эту проблему на стадии выполнения).
Допуская подобную ошибку, программист почти всегда рассчитывает на то, что результаты вызова алгоритма будут вставлены в приемный контейнер вызовом
insert
. Если вы хотите, чтобы это произошло, так и скажите. В конце концов, STL — всего лишь библиотека, и читать мысли ей не положено. В нашем примере задача решается построением итератора, определяющего начало приемного интервала, вызовом
back_inserter
:
vector<int> values;
transform(values.begin, // Применить transmogrify к каждому
values.end, // объекту вектора values
back_inserter(results), // и дописать значения в конец results
transmogrify);
При использовании итератора, возвращаемого при вызове
back_inserter
, вызывается
push_back
, поэтому
back_inserter
может использоваться со всеми контейнерами, поддерживающими
push_back
(то есть со всеми стандартными последовательными контейнерами:
vector
,
string
,
deque
и
list
). Если вы предпочитаете, чтобы алгоритм вставлял элементы в начало контейнера, Воспользуйтесь
front_inserter
. Во внутренней реализации
front_inserter
используется
push_front
, поэтому
front_inserter
работает только с контейнерами, поддерживающими эту функцию (то есть