В начале главы 1 я упоминал о том, что львиная доля репутации STL связана с контейнерами, и это вполне объяснимо. Контейнеры обладают массой достоинств и упрощают повседневную работу бесчисленных программистов C++. Но и алгоритмы STL тоже по-своему замечательны и в той же степени облегчают бремя разработчика. Существует более 100 алгоритмов, и встречается мнение, что они предо-ставляют программисту более гибкий инструментарий по сравнению с контейнерами (которых всего-то восемь!). Возможно, недостаточное применение алгоритмов отчасти и объясняется их количеством. Разобраться в восьми типах контейнеров проще, чем запомнить имена и предназначение многочисленных алгоритмов.
В этой главе я постараюсь решить две основные задачи. Во-первых, я представлю некоторые малоизвестные алгоритмы и покажу, как с их помощью упростить себе жизнь. Не беспокойтесь, вам не придется запоминать длинные списки имен. Алгоритмы, представленные в этой главе, предназначены для решения повседневных задач — сравнение строк без учета регистра символов, эффективный поиск n
объектов, в наибольшей степени соответствующих заданному критерию, обобщение характеристик всех объектов в заданном интервале и имитация copy_if
(алгоритм из исходной реализации HP STL, исключенный в процессе стандартизации).
Во-вторых, я научу вас избегать стандартных ошибок, возникающих при работе с алгоритмами. Например, при вызове алгоритма remove и его родственников remove_if
и unique
необходимо точно знать, что эти алгоритмы делают (и чего они remove
для интервала, содержащего указатели. Многие алгоритмы работают только с отсортированными интервалами, и программист должен понимать, что это за алгоритмы и почему для них установлено подобное ограничение. Наконец, одна из наиболее распространенных ошибок, допускаемых при работе с алгоритмами, заключается в том, что программист предлагает алгоритму записать результаты своей работы в несуществующую область памяти. Я покажу, как это происходит и как предотвратить эту ошибку.
Возможно, к концу главы вы и не будете относиться к алгоритмам с тем же энтузиазмом, с которым обычно относятся к контейнерам, но по крайней мере будете чаще применять их в своей работе.
Совет 30. Следите за тем, чтобы приемный интервал имел достаточный размер
Контейнеры STL автоматически увеличиваются с добавлением новых объектов (функциями insert
, push_front
, push_back
и т. д.). Автоматическое изменение размеров чрезвычайно удобно, и у многих программистов создается ложное впечатление, что контейнер сам обо всем позаботится и им никогда не придется следить за наличием свободного места. Если бы так!
Проблемы возникают в ситуации, когда программист
int transmogrify(int х); // Функция вычисляет некое новое значение
// по переданному параметру х
vector
… // Заполнение вектора values данными
vector
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 позволит обнаружить эту проблему на стадии выполнения).