Почему std :: for_each (from, to, function) возвращает функцию? - PullRequest
29 голосов
/ 12 января 2010

Я только что прочитал код для std::for_each:

template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
  for ( ; first!=last; ++first ) f(*first);
  return f;
}

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

Ответы [ 6 ]

45 голосов
/ 12 января 2010

Это позволяет вам накапливать состояние в вашей функции, а затем возвращать его в свой вызывающий код. Например, ваша функция (как класс функтора) может иметь член int для подсчета количества вызовов, которые она вызвала.

Вот страница с некоторыми примерами: http://xenon.arcticus.com/c-morsels-std-for-each-functors-member-variables

10 голосов
/ 29 ноября 2012

Может быть, у Алекса Степанова была парадигма функционального программирования, но вы обнаружите, что и std::accumulate, и std::for_each передают свои операнды (функцию и накопленное значение) по значению, а не по ссылке. Таким образом:

class MyFunctor
{
   Y val;
   public:
     MyFunctor() : val() {}

     void operator()( X const& x )
     {
        // do something to modify val based on x
     }

     Y getValue() const { return val; }   
};

Теперь, если вы попробуете:

MyFunctor f;
for_each( coll.begin(), coll.end(), f );
Y y = f.getValue();

Это не будет работать, потому что for_each имел дело с копиями f. Конечно, у вас может быть экземпляр shared_ptr<Y> внутри, который будет указывать на тот же экземпляр. Вы также можете сделать val внутри MyFunctor ссылкой, создать его вне цикла и передать в MyFunctor.

Однако язык позволяет вам просто:

Y y = for_each( coll.begin(), coll.end(), MyFunctor() ).getValue();

красиво и удобно, все в одну строчку.

Чтобы сделать то же самое с std::accumulate было бы сделано так:

class MyFunctor2
{
public:
      Y operator()( Y y, X const& x ) const
      {
         //    create a new Y based on the old one and x
        ...
      }
};

Y y = std::accumulate( coll.begin(), coll.end(), Y(), MyFunctor2() );

Вы можете использовать функцию (или в C ++ 11 лямбду) вместо функтора. Обратите внимание, что у функтора нет состояния, и вы передаете инициализированный объект в качестве параметра, который может быть временным.

Теперь мы знаем, что Y можно копировать. std::accumulate использует by value для Y, а не модификацию на месте. Между прочим, когда модификация на месте действительно более эффективна, существует обходной путь без написания нового алгоритма (например, накопления2, который использует + = или ссылочную модификацию) с использованием сигнатуры функции:

Y * func( Y* py, X const & ); // function modifies *py in-place then returns py

затем звоните:

Y y;
std::accumulate( coll.begin(), coll.end(), &y, func );

Мы «знаем», что возвращаемое значение будет & y. Мы можем использовать это, если хотим получить доступ к члену Y в одном месте, например,

Y y;
Z z = std::accumulate( coll.begin(), coll.end(), &y, func )->getZ();

Кстати, ключевое отличие копии в for_each и копии в accumulate заключается в сложности / количестве копий, которые она сделает. С for_each будет сделано не более 2 копий вашего функтора: одна в качестве параметра в функцию и одна в возвращаемом. Я говорю «максимум», потому что Оптимизация возвратного значения может уменьшить второй из этих копий. С accumulate он копирует с каждым элементом в коллекции, т.е. O(N), а не с постоянным временем. Таким образом, если копия слегка дорогая, то двойная копия в функторе не будет большим расходом, повторяющимся небольшое количество раз по большим коллекциям, тогда как для накопления это будет (и предложение будет взломать указатель).

3 голосов
/ 12 января 2010

Возвращение функции в основном превращает std::for_each в посредственную имитацию std::accumulate. Это позволяет вам что-то накапливать в функции / функторе, а затем извлекать это накопленное значение, когда это будет сделано. Практически каждый раз, когда вы думаете, что это может быть полезно, вам, вероятно, стоит подумать об использовании std::accumulate.

2 голосов
/ 12 января 2010

Если вы передаете объект функции, он же функтор, и он имеет состояние, возвращение объекта функции позволяет получить доступ к его состоянию после итерации последовательности. Допустим, у вас был объект функции, который вычисляет три различные переменные из последовательности и хранит их в переменных-членах. Каждый раз, когда вызывается функтор, вы обновляете счетчики. Если for_each не вернул объект, как бы вы получили результат?

Обратите внимание ... именно поэтому вы всегда должны реализовывать функцию копирования и присваивания для объектов функций с состоянием.

2 голосов
/ 12 января 2010

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

0 голосов
/ 12 января 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...