}
… // После завершения цикла
// i указывает на искомый элемент
// или совпадает с v.end()
То же самое можно сделать и при помощи find_if
, но для этого придется воспользоваться нестандартным адаптером объекта функции — например, compose2
из реализации SGI (см. совет 50):
vector::iterator i =
find_if(v.begin(), v.end(), // Найти первое значение val ,
compose2(logical_and(), // для которого одновременно
bind2nd(greater(), x), // истинны условия
bind2nd(less(), y))); // val>x , и val
Но даже если бы нестандартные компоненты не использовались, многие программисты полагают, что вызов алгоритма значительно уступает циклу по наглядности, и я склонен с ними согласиться (см. совет 47).
Вызов find_if
можно было бы упростить за счет выделения логики проверки в отдельный класс функтора.
template
class BetweenValues:
public unary_function {// См. совет 40
public:
BetweenValues(const T& lowValue, const T& highValue) :
lowVal(lowValue), highVal(highValue) {}
bool operator()(const T& val) const {
return val > lowVal && val < highVal;
}
private:
T lowVal;
T highVal;
};
…
vector iterator i = find_if(v.begin(), v.end(),
BetweenValues(x, y));
Однако у такого решения имеются свои недостатки. Во-первых, создание шаблона BetweenValues
требует значительно большей работы, чем простое написание тела цикла. Достаточно посчитать строки в программе: тело цикла — одна строка, BetweenValues
— четырнадцать строк. Соотношение явно не в пользу алгоритма. Во-вторых, описание критерия поиска физически отделяется от вызова. Чтобы понять смысл вызова find_if
, необходимо найти определение BetweenValues
, но оно должно располагаться вне функции, содержащей вызов find_if
. Попытка объявить BetweenValues
внутри функции, содержащей вызов find_if
:
{ // Начало функции
…
template
class BetweenValues: public unary_function {…};
vector::iterator i = find_if(v.begin(), v.end(),
BetweenValues(x, у));
} // Конец функции
не компилируется, поскольку шаблоны не могут объявляться внутри функций. Если попробовать обойти это ограничение посредством реализации BetweenValues
в виде класса:
{ // Начало функции
…
class BetweenValues: public unary_function {…};
vector::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
повышает уровень абстракции и упрощает программирование, документирование, усовершенствование и сопровождение программы.
Читать дальше