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

ЖАНРЫ

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

Последний пример не столь тривиален. В нем вычисляется среднее арифметическое по интервалу точек, представленных структурами следующего вида:

struct Point {

 Point(double initX, double initY): x(initX), y(initY) {}

 double x, y;

};

В этом примере обобщающей функцией будет функтор

PointAverage
, но перед рассмотрением класса этого функтора стоит рассмотреть его использование при вызове
accumulate
:

list<Point> 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<Point, Point, Point> {

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<Point, void>{ // См. совет 40

public:

 PointAverage: xSum(0), ySum(0), numPoints(0) {}

 void operator(const Point& p) {

++numPoints;

xSum += p.x;

ySum += p.y;

 }

 Point result const {

return Point(xSum/numPoints, ySum/numPoints);

 }

private:

 size t numPoints;

 double xSum;

 double ySum;

};

list<Point> lp;

Point avg = for_each(lp.begin, lp.end, PointAverage).result;

Лично я предпочитаю обобщать интервальные данные при помощи

accumulate
, поскольку мне кажется, что этот алгоритм наиболее четко передает суть происходящего, однако
foreach
тоже работает, а вопрос побочных эффектов для
for_each
не так принципиален, как для
accumulate
. Словом, для обобщения интервальных данных могут использоваться оба алгоритма; выберите тот, который вам лучше подойдет.

Возможно, вас интересует, почему у

for_each
параметр-функция может иметь побочные эффекты, а у
accumulate
— не может? Представьте, я бы тоже хотел это знать. Что ж, дорогой читатель, некоторые тайны остаются за пределами наших познаний. Чем
accumulate
принципиально отличается от
for_each
? Пока я еще не слышал убедительного ответа на этот вопрос.

Функции, функторы и классы функций

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

find_if
, конструкции
for_each
и
transform
без них теряют смысл, а адаптеры типа
not1
и
bind2nd
активно создают их.

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