Программисты C++ часто опираются на предположения. Например, они предполагают, что копирующие конструкторы действительно копируют (как показано в совете 8, невыполнение этого правила приводит к удивительным последствиям). Они предполагают, что в результате взятия адреса объекта вы получаете указатель на этот объект (в совете 18 рассказано, что может произойти в противном случае). Они предполагают, что адаптеры bind1st
и not2
могут применяться к объектам функций (см. совет 40). Они предполагают, что оператор +
выполняет сложение (кроме объектов string
, но знак «+» традиционно используется для выполнения конкатенации строк), что оператор -
вычитает, а оператор ==
проверяет равенство. И еще они предполагают, что функция less
эквивалентна operator<
.
В действительности operator<
представляет собой нечто большее, чем реализацию less
по умолчанию — он соответствует ожидаемому поведению less
. Если less
вместо вызова operator<
делает что-либо другое, это нарушает ожидания программистов и вступает в противоречие с «принципом минимального удивления». Конечно, поступать так не стоит — особенно если без этого можно обойтись.
В STL нет ни одного случая использования less
, когда программисту бы не предоставлялась возможность задать другой критерий сравнения. Вернемся к исходному примеру с контейнером multiset
, упорядоченному по атрибуту maxSpeed
. Задача решается просто: для выполнения нужного сравнения достаточно создать класс функтора практически с любым именем, кроме less
. Пример:
struct MaxSpeedCompare:
public binary_function {
bool operator()(const Widget& lhs, const Widget& rhs) const {
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
При создании контейнера multiset
достаточно указать тип сравнения MaxSpeedCompare
, тем самым переопределяя тип сравнения по умолчанию ( less
):
multiset widgets;
Смысл этой команды абсолютно очевиден: мы создаем контейнер multiset
с элементами Widget
, упорядоченными в соответствии с классом функтора MaxSpeedCompare
. Сравните со следующим объявлением:
multiset widgets;
В нем создается контейнер multiset
объектов Widget
, упорядоченных по стандартному критерию. Строго говоря, упорядочение производится по критерию less
, но большинство программистов будет полагать, что сортировка производится функцией operator<
. Не нужно обманывать их ожидания и подменять определение less
. Если вы хотите использовать less
(явно или косвенно), проследите за тем, чтобы этот критерий был эквивалентен operator<
. Если объекты должны сортироваться по другому критерию, создайте специальный класс функтора и назовите его как-нибудь иначе.
STL традиционно характеризуется как совокупность контейнеров, итераторов, алгоритмов и объектов функций, однако программирование в STL заключает в себе нечто большее. Этот термин означает, что программист способен правильно выбирать между циклами, алгоритмами или функциями контейнеров; знает, в каких случаях equal_range
предпочтительнее lower_bound
, когда lower_bound
предпочтительнее find
и когда find
превосходит equal_range
. Термин означает, что программист умеет повышать быстродействие алгоритма посредством замены функций эквивалентными функторами и избегает написания непереносимого или плохо читаемого кода. Более того, к этому понятию даже относится умение читать сообщения об ошибках компилятора, состоящие из нескольких тысяч символов, и хорошее знание Интернет-ресурсов, посвященных STL (документация, расширения и даже полные реализации).
Да, для программирования в STL необходимо много знать, и большая часть этой информации приведена в данной главе.
Совет 43. Используйте алгоритмы вместо циклов
Каждому алгоритму передается по крайней мере одна пара итераторов, определяющих интервал объектов для выполнения некоторой операции. Так, алгоритм min_element
находит минимальное значение в интервале, алгоритм accumulate
вычисляет сводную величину, характеризующую интервал в целом (см. совет 37), а алгоритм partition
делит элементы интервала на удовлетворяющие и не удовлетворяющие заданному критерию (см. совет 31). Чтобы алгоритм мог выполнить свою задачу, он должен проанализировать каждый объект в переданном интервале (или интервалах), для чего объекты в цикле перебираются от начала интервала к концу. Некоторые алгоритмы (такие как find
и find_if
) могут вернуть управление до завершения полного перебора, но и в этих алгоритмах задействован внутренний цикл. Ведь даже алгоритмы find
и find_if
должны проанализировать все элементы интервала, прежде чем принять решение об отсутствии искомого элемента.
Читать дальше