struct Point {
Point(double initX, double initY): x(initX), y(initY) {}
double x, y;
};
В этом примере обобщающей функцией будет функтор PointAverage
, но перед рассмотрением класса этого функтора стоит рассмотреть его использование при вызове accumulate
:
list lp;
…
Point avg = // Вычисление среднего
accumulate(lp.begin(), lp.end(),// арифметического по точкам,
Point(0,0), PointAverage()); // входящим в список lр
Просто и бесхитростно, как и должно быть. На этот раз в качестве начального значения используется объект
Point, соответствующий началу координат, а нам остается лишь помнить о необходимости исключения этой точки из вычислений.
Функтор PointAverage
отслеживает количество обработанных точек, а также суммы их компонентов x
и y
. При каждом вызове он обновляет данные и возвращает средние координаты по обработанным точкам. Поскольку для каждой точки в интервале функтор вызывается ровно один раз, он делит суммы по составляющим x
и y
на количество точек в интервале. Начальная точка, переданная при вызове accumulate
, игнорируется.
class PointAverage:
publiс binary_function {
public:
PointAverage(): xSum(0), ySum(0), numPoints(0) {}
const Point operator() (const Point& avgSoFar, const Point& p) {
++numPoints;
xSum += p.x;
ySum += p.y;
return Point(xSum/numPoints, ySum/numPoints);
}
private:
size_t numPoints;
double xSum;
double ySum;
};
Такое решение прекрасно работает, и лишь из-за периодических контактов с неординарно мыслящими личностями (многие из которых работают в Комитете по стандартизации) я могу представить себе реализации STL, в которых возможны проблемы. Тем не менее, PointAverage
нарушает параграф 2 раздела 26.4.1 Стандарта, который, как вы помните, запрещает побочные эффекты по отношению к функции,передаваемой accumulate
. Модификация переменных numPoints
, xSum
и ySum
относится к побочным эффектам, поэтому с технической точки зрения приведенный выше фрагмент приводит к непредсказуемым последствиям. На практике трудно представить, что приведенный код может не работать, но чтобы моя совесть была чиста, я обязан специально оговорить это обстоятельство.
Впрочем, у меня появляется удобная возможность упомянуть о for_each
— другом алгоритме, который может использоваться для обобщения интервалов. На for_each
не распространяются ограничения, установленные для accumulate
. Алгоритм for_each
, как и accumulate
, получает интервал и функцию (обычно в виде объекта функции), вызываемую для каждого элемента в интервале, однако функция, передаваемая for_each
, получает только один аргумент (текущий элемент интервала), а после завершения работы for_each
возвращает свою функцию (а точнее, ее копию — см. совет 38). Что еще важнее, переданная (и позднее возвращаемая) функция может обладать побочными эффектами.
Помимо побочных эффектов между for_each
и accumulate
существуют два основных различия. Во-первых, само название accumulate
ассоциируется с вычислением сводного значения по интервалу, а название for_each
скорее предполагает выполнение некой операции с каждым элементом интервала. Алгоритм for_each
может использоваться дя вычисления сводной величины, но такие решения по наглядности уступают accumulate
.
Во-вторых, accumulate непосредственно возвращает вычисленное значение, а for_each
возвращает объект функции, используемый для дальнейшего получения информации. В C++ это означает, что в класс функтора необходимо включить функцию для получения искомых данных.
Ниже приведен предыдущий пример, в котором вместо accumulate используется for_each
:
struct Point{…}; // См. ранее
class PointAverage;
public unary_function{ // См. совет 40
public:
PointAverage(): xSum(0), ySum(0), numPoints(0) {}
voidoperator()( const Point& p) {
++numPoints;
xSum += p.x;
ySum += p.y;
}
Point result() const {
return Point(xSum/numPoints, ySum/numPoints);
}
private:
size t numPoints;
Читать дальше