set s;
…
transform(s.begin(), s.end(),
ostream_iterator(cout, "\n"),
mem_fun_ref(&string::size)
);
Проблема возникает из-за ошибки в работе с константными функциями классов (такими как string::size
) в этой конкретной платформе STL. Обходное решение заключается в использовании объекта функции:
struct StringSize:
public_unary_function {// См. совет 40
string::size_type operator()(const string& s) const {
return s.size();
}
};
transform (s.begin(), s.end(),
ostream_iterator(cout, "\n"), StringSize();
Существуют и другие обходные решения, но приведенный фрагмент хорош не только тем, что он компилируется на всех известных мне платформах 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 объекты функций часто оказываются полезнее.
Совет 47. Избегайте «нечитаемого» кода
Допустим, имеется вектор 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());
Всего одна команда, и задача решена. Все просто и прямолинейно. Никаких проблем. Правда?
Читать дальше