Преимущества цикла std :: for_each over for - PullRequest
155 голосов
/ 12 января 2010

Есть ли какие-либо преимущества std::for_each над for петлей? Мне кажется, что std::for_each только мешает удобочитаемости кода. Почему тогда некоторые стандарты кодирования рекомендуют его использовать?

Ответы [ 20 ]

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

Хорошая вещь с C ++ 11 (ранее называвшаяся C ++ 0x) заключается в том, что эта утомительная дискуссия будет решена.

Я имею в виду, что никто в здравом уме, который хочет перебрать всю коллекцию, все равно не будет использовать это

for(auto it = collection.begin(); it != collection.end() ; ++it)
{
   foo(*it);
}

или это

for_each(collection.begin(), collection.end(), [](Element& e)
{
   foo(e);
});

когда доступен петля for loop :

for(Element& e : collection)
{
   foo(e);
}

Этот вид синтаксиса уже давно доступен в Java и C #, и на самом деле в каждом недавнем коде Java или C #, который я видел, имеется намного больше циклов foreach, чем классических циклов for.

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

Вот несколько причин:

  1. Кажется, это затрудняет читабельность только потому, что вы к нему не привыкли и / или не используете правильные инструменты, чтобы сделать его действительно простым. (см. boost :: range и boost :: bind / boost :: lambda для помощников. Многие из них перейдут в C ++ 0x и сделают for_each и связанные функции более полезными.)

  2. Позволяет написать алгоритм поверх for_each, который работает с любым итератором.

  3. Уменьшает вероятность глупых ошибок при печати. ​​

  4. Это также открывает ваш разум для остальных алгоритмов STL, таких как find_if, sort, replace и т. Д., И они больше не будут выглядеть так странно. Это может быть огромная победа.

Обновление 1:

Самое главное, это поможет вам выйти за пределы for_each против циклов for, как будто это все, что есть, и посмотреть на другие STL-логи, такие как find / sort / partition / copy_replace_if, выполнение Parallell или что-то еще

Большая часть обработки может быть написана очень кратко, используя «остальную часть» братьев и сестер for_each, но если все, что вам нужно, это написать цикл for с различной внутренней логикой, то вы никогда не научитесь использовать их, и Вы закончите изобретать колесо снова и снова.

И (скоро будет доступен диапазон в стиле for_each):

for_each(monsters, boost::mem_fn(&Monster::think));

Или с C ++ x11 лямбдами:

for_each(monsters, [](Monster& m) { m.think(); });

ИМО более читабельно, чем:

for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
    i->think();
} 

Также это (или с лямбдами, см. Другие):

for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));

является более кратким, чем:

for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
    my_monkey->eat(*i);
} 

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

Обновление 2 : я написал собственные однострочные оболочки stl-algos, которые работают с диапазонами вместо пары итераторов. Однажды выпущенный boost :: range_ex будет включать его, и, возможно, он будет присутствовать и в C ++ 0x?

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

for_each является более общим. Вы можете использовать его для перебора любого типа контейнера (передавая итераторы начала / конца). Вы можете поменять контейнеры под функцией, которая использует for_each, без необходимости обновления кода итерации. Вам нужно учитывать, что в мире есть другие контейнеры, кроме std::vector и простые старые массивы C, чтобы увидеть преимущества for_each.

Главный недостаток for_each заключается в том, что он использует функтор, поэтому синтаксис неуклюжий. Это исправлено в C ++ 0x с введением лямбд:

std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
    i+= 10;
});

Это не будет странным для вас через 3 года.

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

Лично, в любой момент, когда мне нужно было бы изо всех сил использовать std::for_each (написать специальные функторы / сложные boost::lambda с), я нахожу, что BOOST_FOREACH и C ++ 0x основаны на диапазоне для яснее:

BOOST_FOREACH(Monster* m, monsters) {
     if (m->has_plan()) 
         m->act();
}

против

std::for_each(monsters.begin(), monsters.end(), 
  if_then(bind(&Monster::has_plan, _1), 
    bind(&Monster::act, _1)));
11 голосов
/ 12 января 2010

очень субъективно, некоторые скажут, что использование for_each сделает код более читабельным, поскольку позволяет обрабатывать разные коллекции с одинаковыми соглашениями.for_each itlef реализован в виде цикла

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

, поэтому вам решать, что именно вам подходит.

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

Как и во многих функциях алгоритма, первоначальная реакция - думать, что использовать foreach более невозможно, чем цикл. Это было темой многих пламенных войн.

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

Еще одно преимущество заключается в том, что когда я вижу foreach, я знаю , что либо каждый элемент будет обработан, либо будет сгенерировано исключение.

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

Вот пример запутанного цикла для :

for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i)
{
   /////////////////////////////////////////////////////////////////////
   // Imagine a page of code here by programmers who don't refactor
   ///////////////////////////////////////////////////////////////////////
   if(widget->Cost < calculatedAmountSofar)
   {
        break;
   }
   ////////////////////////////////////////////////////////////////////////
   // And then some more code added by a stressed out juniour developer
   // *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#*
   /////////////////////////////////////////////////////////////////////////
   for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip)
   {
      if(ip->IsBroken())
      {
         return false;
      }
   }
}
10 голосов
/ 12 января 2010

Вы в основном правы: в большинстве случаев std::for_each - это чистый убыток.Я бы пошел так далеко, чтобы сравнить for_each с goto.goto обеспечивает максимально универсальное управление потоком данных - вы можете использовать его для реализации практически любой другой структуры управления, которую только можете себе представить.Однако именно эта универсальность означает, что видение goto в изоляции практически ничего не говорит вам о том, что он должен делать в этой ситуации.В результате, почти никто в здравом уме не использует goto, кроме как в крайнем случае.

Среди стандартных алгоритмов for_each во многом аналогичен - его можно использовать для реализации практически чего угодно,Это означает, что видение for_each практически ничего не говорит вам о том, для чего он используется в этой ситуации.К сожалению, отношение людей к for_each о том, где их отношение к goto было, скажем, в 1970 или около того - немногие люди поняли, что его следует использовать только как последнийприбегают к помощи, но многие все еще считают его основным алгоритмом, и редко, если вообще используют какой-либо другой.Подавляющее большинство времени, даже быстрый взгляд показал бы, что одна из альтернатив была значительно выше.

Например, я почти уверен, что потерял счет, сколько раз я виделлюди пишут код для распечатки содержимого коллекции, используя for_each.Судя по сообщениям, которые я видел, это может быть наиболее распространенное использование for_each.В итоге они получают что-то вроде:

class XXX { 
// ...
public:
     std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};

И их пост спрашивает о том, какую комбинацию bind1st, mem_fun и т. Д. Им нужно сделать что-то вроде:

std::vector<XXX> coll;

std::for_each(coll.begin(), coll.end(), XXX::print);

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

К счастью, есть намного лучший способ.Добавьте обычную перегрузку потоковой вставки для XXX:

std::ostream &operator<<(std::ostream *os, XXX const &x) { 
   return x.print(os);
}

и используйте std::copy:

std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));

Это работает - и практически не требует никакой работы, чтобы выяснить, что этопечатает содержимое от coll до std::cout.

8 голосов
/ 13 января 2010

Преимущество написания функционала для большей читабельности, может не проявляться при for(...) и for_each(...).

Если вы используете все алгоритмы в functions.h, вместо использования циклов for, код становится намного более читабельным;

iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...);
iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...);
std::transform(forest.begin(), forest.end(), firewood.begin(), ...);
std::for_each(forest.begin(), forest.end(), make_plywood);

намного более читабельно, чем;

Forest::iterator longest_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (*it > *longest_tree) {
     longest_tree = it;
   }
}

Forest::iterator leaf_tree = it.begin();
for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
   if (it->type() == LEAF_TREE) {
     leaf_tree  = it;
     break;
   }
}

for (Forest::const_iterator it = forest.begin(), jt = firewood.begin(); 
     it != forest.end(); 
     it++, jt++) {
          *jt = boost::transformtowood(*it);
    }

for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{
    std::makeplywood(*it);
}

И это то, что я считаю очень хорошим, обобщить циклы for на одну строку функций =)

6 голосов
/ 09 августа 2016

Easy: for_each полезно, когда у вас уже есть функция для обработки каждого элемента массива, поэтому вам не нужно писать лямбда-выражения. Конечно, это

for_each(a.begin(), a.end(), a_item_handler);

лучше

for(auto& item: a) {
    a_item_handler(a);
}

Кроме того, цикл for ранжированного цикла перебирает только целые контейнеры от начала до конца, тогда как for_each более гибок.

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

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

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

struct myfunctor {
   void operator()( int arg1 ) { code }
};
void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), myfunctor() );
   // more code
}

Обратите внимание, что если вы хотите выполнить определенную операцию с каждым объектом, вы можете использовать std::mem_fn, или boost::bind (std::bind в следующем стандарте), или boost::lambda (лямбда в следующем стандарте) для сделать это проще:

void function( int value );
void apply( std::vector<X> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
   // code
}

Который не менее читабелен и более компактен, чем версия, свернутая вручную, если у вас есть функция / метод для вызова на месте. Реализация может предоставить другие реализации цикла for_each (подумайте о параллельной обработке).

Будущий стандарт по-разному устраняет некоторые недостатки, он допускает использование локально определенных классов в качестве аргументов для шаблонов:

void apply( std::vector<int> const & v ) {
   // code
   struct myfunctor {
      void operator()( int ) { code }
   };
   std::for_each( v.begin(), v.end(), myfunctor() );
   // code
}

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

void apply( std::vector<int> const & v ) {
   // code
   std::for_each( v.begin(), v.end(), 
      []( int ) { // code } );
   // code
}

Даже если для случая for_each найдется специальная конструкция, которая сделает его более естественным:

void apply( std::vector<int> const & v ) {
   // code
   for ( int i : v ) {
      // code
   }
   // code
}

Я склонен смешивать конструкцию for_each с петлями, скрученными вручную. Когда мне нужен только вызов существующей функции или метода (for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )), я обращаюсь к конструкции for_each, которая убирает из кода много вещей итератора. Когда мне нужно что-то более сложное, и я не могу реализовать функтор на пару строк выше фактического использования, я запускаю свой собственный цикл (сохраняет операцию на месте). В некритических разделах кода я мог бы пойти с BOOST_FOREACH (коллега включил меня в это)

...