Существуют и другие обходные решения, но приведенный фрагмент хорош не только тем, что он компилируется на всех известных мне платформах STL. Он также делает возможной подстановку вызова string::size, что почти наверняка невозможно в предыдущем фрагменте с передачей mem_fun_ref(&string::size). Иначе говоря, определение класса функтора StringSize не только обходит недоработки компилятора, но и может улучшить быстродействие программы.

Другая причина, по которой объекты функций предпочтительнее обычных функций, заключается в том, что они помогают обойти хитрые синтаксические ловушки. Иногда исходный текст, выглядящий вполне разумно, отвергается компилятором по законным, хотя и неочевидным причинам. Например, в некоторых ситуациях имя экземпляра, созданного на базе шаблона функции, не эквивалентно имени функции. Пример:

template                // Вычисление среднего

FPType average(FPType val1, FPType val2) // арифметического двух

{                                        //вещественных чисел

 return (val1 + val2)/2;

};

template

void writeAverages(InputIter begin1, // Вычислить попарные

 InputIter end1,                     // средние значения

 InputIter begin2,                   // двух серий элементов

 ostream& s) {                       // в потоке

 transform(begin1, end1, begin2,

  ostream_iterator::value_type>(s, "\n"),

  average::value_type>); // Ошибка?

}

Многие компиляторы принимают этот код, но по Стандарту C++ он считается недопустимым. Дело в том, что теоретически может существовать другой шаблон функции с именем average, вызываемый с одним параметром-типом. В этом случае выражение average::value_type> становится неоднозначным, поскольку непонятно, какой шаблон в нем упоминается. В конкретном примере неоднозначность отсутствует, но некоторые компиляторы на вполне законном основании все равно отвергают этот код. Решение основано на использовании объекта функции:

template

struct Average:

 public binary_function { // См. совет 40

 FPType operator(FPType val1, FPType val2) const {

  return average(val1, val2);

 }

};

template

void writeAverages(InputIter1 begin1, InputIter1 end1,

 InputIter2 begin2, ostream& s) {

 transform(begin1, end1, begin2,

 ostream_iterator::value_type>(s, "\n"),

  Average::value_type);

}

Новая версия должна приниматься любым компилятором. Более того, вызовы Average::operator внутри transform допускают подстановку кода, что не относится к экземплярам приведенного выше шаблона average, поскольку average является шаблоном функции, а не объекта функции.

Таким образом, преимущество объектов функций в роли параметров алгоритмов не сводится к простому повышению эффективности. Объекты функций также обладают большей надежностью при компиляции кода. Бесспорно, «настоящие» функции очень важны, но в области эффективного программирования в STL объекты функций часто оказываются полезнее.

<p>Совет 47. Избегайте «нечитаемого» кода</p>

Допустим, имеется вектор vector. Из этого вектора требуется удалить все элементы, значение которых меньше х, но оставить элементы, предшествующие последнему вхождению значения, не меньшего у. В голову мгновенно приходит следующее решение:

vector v;

int х, у;

v.erase(

 remove_if(find_if(v.rbegin, v.rend,

 bind2nd(greater_equal, y)).base,

 v.end,

bind2nd(less,x)),

 v.end);

Всего одна команда, и задача решена. Все просто и прямолинейно. Никаких проблем. Правда?

Не будем торопиться с выводами. Считаете ли вы приведенный код логичным и удобным в сопровождении? «Нет!» — воскликнет большинство программистов C++ с ужасом и отвращением. «Да!» — скажут считанные единицы с явным удовольствием. В этом и заключается проблема. То, что один программист считает выразительной простотой, другому представляется адским наваждением.

Насколько я понимаю, приведенный выше фрагмент вызывает беспокойство по двум причинам. Во-первых, он содержит слишком много вызовов функций. Чтобы понять, о чем идет речь, приведу ту же команду, в которой имена функций заменены обозначениями fn:

Перейти на страницу:

Похожие книги