Чтение онлайн

ЖАНРЫ

Эффективное использование STL
Шрифт:

void sort(vector<double>::iterator first, // Начало интервала

 vector<double>:iterator last, // Конец интервала

 bool (*comp)(double, double)); // Функция сравнения

Поскольку

comp
является указателем на функцию, при каждом его использовании внутри sort происходит косвенный вызов функции (то есть вызов через указатель). Большинство компиляторов не пытается подставлять вызовы функций, вызываемых через указатели, даже если функция объявлена с ключевым словом
inline
и оптимизация выглядит очевидной. Почему? Наверное, потому, что разработчики компиляторов не считают нужным ее реализовать. Пожалейте их — народ постоянно чего-нибудь
требует, а успеть все невозможно. Впрочем, это вовсе не означает, что требовать не нужно.

Подавление подстановки кода функций объясняет один факт, который кажется невероятным многим опытным программистам C: функция C++

sort
почти всегда превосходит по скорости функцию C
qsort
. Конечно, в C++ приходится создавать экземпляры шаблонов функций и вызывать
operator
, тогда как в C все ограничивается простым вызовом функции, однако все «излишества» C++ теряются во время компиляции. На стадии выполнения
sort
обращается к подставленной функции сравнения (при условии, что функция была объявлена с ключевым словом
inline
, а ее тело доступно на стадии компиляции), тогда как
qsort
вызывает функцию сравнения через указатель. Результат —
sort
работает гораздо быстрее. В моих тестах с вектором, содержащим миллион чисел
double
, превосходство по скорости достигало 670%, но я не призываю верить мне на слово. Вы легко убедитесь в том, что при передаче объектов функций в качестве параметров алгоритмов «плата за абстракцию» превращается в «премию за абстракцию».

Существует и другая причина для передачи объектов функций в параметрах алгоритмов, не имеющая ничего общего с эффективностью. Речь идет о компилируемости программ. По каким-то загадочным причинам некоторые платформы STL отвергают абсолютно нормальный код — это связано с недоработками то ли компилятора, то ли библиотеки, то ли и того и другого. Например, одна распространенная платформа STL отвергает следующий (вполне допустимый) фрагмент, выводящий в

cout
длину всех строк в множестве:

set<string> s;

transform(s.begin, s.end,

 ostream_iterator<string::size_type>(cout, "\n"),

 mem_fun_ref(&string::size)

);

Проблема возникает из-за ошибки в работе с константными функциями классов (такими как

string::size
) в этой конкретной платформе STL. Обходное решение заключается в использовании объекта функции:

struct StringSize:

 public_unary_function<string, string::size_type> { // См. совет 40

 string::size_type operator(const string& s) const {

return s.size;

 }

};

transform (s.begin, s.end,

 ostream_iterator<string::size_type>(cout, "\n"), StringSize;

Существуют и другие обходные решения, но приведенный фрагмент хорош не только тем, что он компилируется на всех известных мне платформах STL. Он также делает возможной подстановку вызова

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

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

имя экземпляра, созданного на базе шаблона функции, не эквивалентно имени функции. Пример:

template<typename FPType> // Вычисление среднего

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

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

 return (val1 + val2)/2;

};

template<typename InputIter1, typename InputIter2>

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

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

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

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

 transform(begin1, end1, begin2,

ostream_iterator<typename iterator_traits<InputIter1>::value_type>(s, "\n"),

average<typename iterator traits<InputIter1>::value_type>); // Ошибка?

}

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

average
, вызываемый с одним параметром-типом. В этом случае выражение
average<typename iterator_traits<InputIter1>::value_type>
становится неоднозначным, поскольку непонятно, какой шаблон в нем упоминается. В конкретном примере неоднозначность отсутствует, но некоторые компиляторы на вполне законном основании все равно отвергают этот код. Решение основано на использовании объекта функции:

template<typename FPType>

struct Average:

 public binary_function<FPType, FPType, FPType> { // См. совет 40

 FPType operator(FPType val1, FPType val2) const {

return average(val1, val2);

 }

};

template<typename InputIter1, typename InputIter2>

void writeAverages(InputIter1 begin1, InputIter1 end1,

 InputIter2 begin2, ostream& s) {

 transform(begin1, end1, begin2,

 ostream_iterator<typename iterator_traits<InputIter1>::value_type>(s, "\n"),

Average<typename iterator_traits<InputIter1>::value_type);

}

Новая версия должна приниматься любым компилятором. Более того, вызовы

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

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

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

Допустим, имеется вектор

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

Поделиться с друзьями: