- •2.3. Итераторы
- •2.4. Функциональные объекты
- •2.5. Адаптеры
- •2.6. Аллокаторы
- •Глава 3. Отличие stl от других библиотек
- •3.1. Расширяемость
- •3.2. Взаимозаменяемость компонентов
- •3.3. Совместимость алгоритмов и контейнеров
- •Глава 4 Итераторы
- •4.1. Входные итераторы
- •4.2. Выходные итераторы
- •4.3. Однонаправленные итераторы
- •4.4. Двунаправленные итераторы
- •4.5. Итераторы с произвольным доступом
2.4. Функциональные объекты
Рассматривавшаяся в предыдущем разделе функция accumulate является весьма
обобщенной в смысле использования ею итераторов, но не настолько обобщена, как могла бы быть, в смысле действий с типами значений, на которые указывают итераторы (эти типы называются просто типами значений итераторов). Определение accumulate предполагает, что имеется определенный для типа значения оператор +, используя его в выражении
init = init + *first;
Таким образом, функция может работать с любыми встроенными числовыми типами C++ или с любыми пользовательскими типами Т, в которых определен такой оператор. Однако абстрактное понятие накопления (accumulation) применимо не только к суммированию; можно точно так же накапливать, например, произведение значений последовательности. Потому STL предоставляет еще одну, более обобщенную версию accumulate:
template <typename Inputlterator, typename T, typename BinaryOperation>
T accumulate(InputIterator first, Inputlterator last, T init, BinaryOperation binary_op)
{
while (first != last)
{
init = binary_op(init, *first);
++first;
}
return init;
}
Вместо записи с оператором + в этом определении вводится еще один параметр — binary_op, представляющий собой бинарную операцию, используемую для объединения значений.
Как воспользоваться этой более обобщенной версией accumulate для вычисления произведения? Если мы определим функцию mult так, как это сделано в следующей программе, то сможем использовать ее в качестве параметра binary_op функции accumulate.
Пример 2.12. Использование обобщенного алгоритма accumulate для
вычисления произведения
#include <iostream>
#include <vector>
#include <cassert>
#include <numeric> // Алгоритм accumulate
using namespace std;
int mult(int x, int y) { return x * y; }
int main()
{
cout << "Использование обобщенного алгоритма "
<< "accumulate для вычисления произведения."
<< endl;
int x[5] = {2, 3, 5, 7, 11};
// Инициализация вектора элементами от х[0] до х[4]:
vector<int> vector1(&x[0], &х[5]);
int product = accumulate(vector1.begin(), vector1.end(), 1, mult);
assert (product == 2310);
cout << " Ok." << endl;
return 0;
}
(Обратите внимание, что мы заменили начальное значение с 0 на 1, которое является правильным "единичным элементом" для умножения.) В приведенном примере мы передаем аргументу accumulate обычную функцию mult. В действительности передается адрес функции, так что мы можем записать аргумент явно, как &mult. Но это только один из способов поддержки в C++ передачи функций другим функциям. Более общей является передача
функциональных объектов, представляющих собой произвольные сущности, которые могут быть применены к нулю или большему количеству аргументов, чтобы получить значение и/или модифицировать состояние вычислений. Кроме обычных функций, функциональными объектами являются объекты типа, определенного как класс или структура с перегруженным оператором вызова функции. Вот пример определения и передачи такого функционального объекта.
Пример 2.13. Использование обобщенного алгоритма accumulate для
вычисления произведения с применением функционального объекта
#include <iostream>
#include <vector>
#include <cassert>
#include <numeric> // Алгоритм accumulate
using namespace std;
class multiply {
public:
int operator()(int x, int y) const { return x * y; }
};
int main()
{
cout << "Использование обобщенного алгоритма "
<< "accumulate для вычисления произведения."
<< endl;
int x[5] = {2, 3, 5, 7, 11};
// Инициализация вектора элементами от х[0] до х[4]:
vector<int> vector1(&x[0], &х[5]);
int product = accumulate(vectorl.begin(),
vectorl.end(), 1,
multiply());
assert (product == 2310);
cout << " Ok." << endl;
return 0;
}
Путем определения оператора вызова функции operator () в классе multiply мы
определяем тип объекта, который может быть применен к списку аргументов, так же, как и функция. Обратите внимание, что объект, переданный accumulate, получается путем вызова конструктора класса по умолчанию multipl(), который автоматически создается компилятором, так как явно определенного конструктора в определении класса нет. Заметим также, что этот объект не требует для хранения памяти, так как представляет собой только определение функции (хотя в некоторых случаях хранение данных в функциональных объектах оказывается удобным).
В чем же преимущество, если таковое имеется, функциональных объектов перед
обычными функциями? Детально этот вопрос будет рассмотрен в главе 8, "Функциональные объекты", но одно из главных преимуществ заключается в том, что объекты, в отличие от обычных функций, могут хранить дополнительную информацию, которая затем может использоваться обобщенными алгоритмами или контейнерами, которым требуется более сложные знания о функции, чем алгоритму accumulate. Имеются также преимущества, связанные с эффективностью и обобщенностью.
Перед тем как завершить рассмотрение этой темы, мы должны упомянуть, что в
примере 2.13 в действительности нет необходимости определять класс multiply, поскольку STL уже содержит такое определение, хотя и в более обобщенной форме:
template <typename T>
class multiplies : public binary_function<T, T, T> {
public:
T operator()(const T& x, const T& y) const
{
return x * y;
}
};
Этот класс наследует другой компонент STL, бинарную функцию, назначение которой — хранить дополнительную информацию о функции (этот вопрос будет рассматриваться в главе 8, "Функциональные объекты"). Используя это определение, программу можно переписать следующим образом.
Пример 2.14. Использование обобщенного алгоритма accumulate для
вычисления произведения с применением multiplies
#include <iostream>
#include <vector>
#include <cassert>
#include <numeric> // Алгоритм accumulate
#include <functional> // Класс multiplies
using namespace std;
int main()
{
cout << "Использование обобщенного алгоритма "
<< "accumulate для вычисления произведения."
<< endl;
int x[5] = {2, 3, 5, 7, 11};
// Инициализация вектора элементами от х[0] до х[4]:
vector<int> vector1(&х[0], &х[5]);
int product = accumulate(vector1.begin(),
vector1.end(),
1, multiplies<int>() ) ;
assert (product == 2310);
cout << " Ok." << endl;
return 0;
}
Выражение multiplies<int> () представляет собой вызов конструктора по
умолчанию класса multiplies, инстанцированного с типом int. Несколько других
функциональных объектов показано в четвертом столбце на рис. 2.1.
