На этом я бы хотел завершить данный совет, поскольку финал выглядит довольно убедительно. К сожалению, тема не поддается столь однозначной трактовке.
Действительно, имена алгоритмов информативнее простых циклов, но четкая формулировка действий, выполняемых при каждой итерации, иногда бывает нагляднее вызова алгоритма. Допустим, нам потребовалось найти первый элемент вектора, значение которого лежит в заданном диапазоне
for(; i!=v.end; ++i){ // с v.begin, до нахождения нужного
if(*i > x && *i < y)) break; // элемента или достижения v.end
}
… // После завершения цикла
// i указывает на искомый элемент
// или совпадает с v.end
То же самое можно сделать и при помощи
find_if
, но для этого придется воспользоваться нестандартным адаптером объекта функции — например,
compose2
из реализации SGI (см. совет 50):
vector<int>::iterator i =
find_if(v.begin, v.end, // Найти первое значение val,
compose2(logical_and<bool>, // для которого одновременно
bind2nd(greater<int>, x), // истинны условия
bind2nd(less<int>, y))); // val>x, и val<y
Но даже если бы нестандартные компоненты не использовались, многие программисты полагают, что вызов алгоритма значительно уступает циклу по наглядности, и я склонен с ними согласиться (см. совет 47).
Вызов
find_if
можно было бы упростить за счет выделения логики проверки в отдельный класс функтора.
Однако у такого решения имеются свои недостатки. Во-первых, создание шаблона
BetweenValues
требует значительно большей работы, чем простое написание тела цикла. Достаточно посчитать строки в программе: тело цикла — одна строка,
BetweenValues
— четырнадцать строк. Соотношение явно не в пользу алгоритма. Во-вторых, описание критерия поиска физически отделяется от вызова. Чтобы понять смысл
вызова
find_if
, необходимо найти определение
BetweenValues
, но оно должно располагаться вне функции, содержащей вызов
find_if
. Попытка объявить
BetweenValues
внутри функции, содержащей вызов
find_if
:
{ // Начало функции
…
template<typename T>
class BetweenValues: public unary_function<T, bool> {…};
vector<int>::iterator i = find_if(v.begin, v.end,
BetweenValues<int>(x, у));
} // Конец функции
не компилируется, поскольку шаблоны не могут объявляться внутри функций. Если попробовать обойти это ограничение посредством реализации
BetweenValues
в виде класса:
{ // Начало функции
…
class BetweenValues: public unary_function<int, bool> {…};
vector<int>::iterator i = find_if(v.begin, v.end,
BetweenValues(x, y));
} // Конец функции
все равно ничего не получается, поскольку классы, определяемые внутри функций, являются локальными, а локальные классы не могут передаваться в качестве аргументов шаблонов (как функтор, передаваемый
find_if
). Печально, но классы функторов и шаблоны классов функторов не разрешается определять внутри функций, как бы удобно это ни было.
В контексте борьбы между вызовами алгоритмов и циклами это означает, что выбор определяется исключительно содержимым цикла. Если алгоритм уже умеет делать то, что требуется, или нечто очень близкое, вызов алгоритма более нагляден. Если задача элементарно решается в цикле, а при использовании алгоритма требует сложных нагромождений адаптеров или определения отдельного класса функтора, вероятно, лучше ограничиться циклом. Наконец, если в цикле приходится выполнять очень длинные и сложные операции, выбор снова склоняется в пользу алгоритмов, потому что длинные и сложные операции лучше оформлять в отдельных функциях. После того как тело цикла будет перенесено в отдельную функцию, почти всегда удается передать эту функцию алгоритму (особенно часто — алгоритму
for_each
) так, чтобы полученный код был более наглядным и прямолинейным.
Если вы согласны с тем, что вызовы алгоритмов обычно предпочтительнее циклов, а также с тем, что интервальные функции обычно предпочтительнее циклического вызова одноэлементных функций (см, совет 5), можно сделать интересный вывод: хорошо спроектированная программа C++, использующая STL, содержит гораздо меньше циклических конструкций, чем аналогичная программа, не использующая STL, и это хорошо. Замена низкоуровневых конструкций
for
,
while
и
do
высокоуровневыми терминами
insert
,
find
и
foreach
повышает уровень абстракции и упрощает программирование, документирование, усовершенствование и сопровождение программы.