Преимущества std:: for_each для цикла

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

4b9b3361

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

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

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

Или это

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

когда доступен синтаксис цикла for на основе диапазона:

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

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

130
ответ дан 12 янв. '10 в 14:02
источник

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

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

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

  • Это уменьшает вероятность глупых ошибок ввода.

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

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

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

Много обработки может быть написано очень сжато, используя "остальное" для сиблей for_each, но если все, что вы делаете, это написать цикл for с внутренней логикой, то вы никогда не узнаете, как их использовать, и вы в конечном итоге изобретаете колесо снова и снова.

И (скоро появится доступный стиль диапазона for_each):

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

Или с С++ x11 lambdas:

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

является IMO более читаемым, чем:

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

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

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

Более кратким, чем:

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

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

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

38
ответ дан 12 янв. '10 в 10:43
источник

Лично, в любое время, когда мне понадобится использовать std::for_each (писать специальные функции/сложные boost::lambda s), я нахожу BOOST_FOREACH и С++ 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)));
17
ответ дан 12 янв. '10 в 11:20
источник

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

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

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

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

17
ответ дан 12 янв. '10 в 10:42
источник

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

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

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

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

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

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 янв. '10 в 10:52
источник

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

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

чтобы вы могли выбрать то, что подходит вам.

10
ответ дан 12 янв. '10 в 10:43
источник

В основном вы правы: большую часть времени, 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.

7
ответ дан 12 янв. '10 в 20:04
источник

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

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

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-loops на одну строку. =)

7
ответ дан 13 янв. '10 в 0:12
источник

Цикл 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 (lambdas в следующем стандарте), чтобы упростите это:

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 (сотрудник вошел в него)

4
ответ дан 12 янв. '10 в 11:00
источник

Помимо читаемости и производительности, один из аспектов, который обычно игнорируется, - это последовательность. Существует много способов реализовать цикл for (или while) над итераторами, начиная с:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
    do_something(*iter);
}

в

C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
    do_something(*iter);
    ++iter;
}

со многими примерами между ними на разных уровнях эффективности и потенциала ошибок.

Использование for_each, однако, обеспечивает согласованность, абстрагируя цикл:

for_each(c.begin(), c.end(), do_something);

Единственное, что вам сейчас нужно беспокоиться: реализуете ли вы тело цикла как функцию, функтор или лямбда, используя функции Boost или С++ 0x? Лично я предпочел бы беспокоиться об этом, чем о том, как реализовать или прочитать случайный цикл/while.

3
ответ дан 12 янв. '10 в 11:52
источник

С С++ 11 и двумя простыми шаблонами вы можете написать

        for ( auto x: range(v1+4,v1+6) ) {
                x*=2;
                cout<< x <<' ';
        }

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

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

Вы все еще используете трехмерное выражение for, но теперь, когда вы видите его, вы знаете, что там что-то понимаете, оно не является шаблоном. Я ненавижу шаблон. Я возмущаюсь его существованием. Это не настоящий код, ему нечего учиться, читая его, это еще одна вещь, которая нуждается в проверке. Умственное усилие можно измерить тем, насколько легко получить ржавый контроль при его проверке.

Шаблоны

template<typename iter>
struct range_ { 
                iter begin() {return __beg;}    iter end(){return __end;}
            range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {}
            iter __beg, __end;
};

template<typename iter>
range_<iter> range(iter const &begin, iter const &end)
    { return range_<iter>(begin,end); }
2
ответ дан 18 авг. '13 в 20:13
источник

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

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

лучше, чем

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

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

2
ответ дан 09 авг. '16 в 18:24
источник

for - для цикла, который может итерировать каждый элемент или каждый третий и т.д. for_each предназначен для итерации только каждого элемента. Это видно из его названия. Поэтому более понятно, что вы собираетесь делать в своем коде.

2
ответ дан 12 янв. '10 в 10:47
источник

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

Алгоритм std::for_each можно прочитать как сделать что-то со всеми элементами в диапазоне, что улучшит читаемость. Скажите, что действие, которое вы хотите выполнить, составляет 20 строк, а функция, в которой выполняется действие, также составляет около 20 строк. Это создало бы функцию длиной 40 строк с обычным циклом, и только около 20 с std::for_each, что, вероятно, было бы легче понять.

Функторы для std::for_each более вероятно, будут более универсальными и, следовательно, повторно использоваться, например:

struct DeleteElement
{
    template <typename T>
    void operator()(const T *ptr)
    {
        delete ptr;
    }
};

И в коде у вас будет только один лайнер, например std::for_each(v.begin(), v.end(), DeleteElement()), который немного лучше IMO, чем явный цикл.

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

std::for_each также, как правило, более надежный, поскольку вы с меньшей вероятностью допустите ошибку с диапазоном.

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

То же самое относится к другим std-алгоритмам, таким как find_if, transform и т.д.

2
ответ дан 12 янв. '10 в 13:55
источник

Я нахожу for_each плохой для удобочитаемости. Концепция хорошая, но С++ очень затрудняет запись читаемых, по крайней мере для меня. С++ 0x lamda выражения помогут. Мне очень нравится идея ламды. Однако на первый взгляд я думаю, что синтаксис очень уродлив, и я не уверен на 100%, что я когда-нибудь привыкну к этому. Может быть, через 5 лет я привык к этому и не подумаю, но, возможно, нет. Время покажет:)

Я предпочитаю использовать

vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
  // Do stuff
}

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

Конечно, случаи меняются, это то, что я обычно нахожу лучшим.

1
ответ дан 12 янв. '10 в 14:21
источник

Если вы часто используете другие алгоритмы из STL, для for_each есть несколько преимуществ:

  • Часто это будет проще и меньше подверженности ошибкам, чем цикл for, отчасти потому, что вы будете использоваться для работы с этим интерфейсом, а отчасти потому, что во многих случаях это немного более кратким.
  • Хотя цикл, основанный на диапазоне, может быть еще проще, он менее гибкий (как заметил Адриан МакКарти, он выполняет итерацию по всему контейнеру).
  • В отличие от традиционного цикла, for_each заставляет вас писать код, который будет работать для любого итератора ввода. Ограничение таким образом действительно может быть хорошим, потому что:

    • На самом деле вам может понадобиться адаптировать код для работы в другом контейнере позже.
    • В начале это может научить вас чему-то и/или изменить ваши привычки к лучшему.
    • Даже если вы всегда будете писать для циклов, которые абсолютно эквивалентны, другие люди, которые изменяют один и тот же код, могут не делать этого, не предлагая использовать for_each.
  • Использование for_each иногда делает более очевидным, что вы можете использовать более определенную функцию STL для выполнения того же самого. (Как в примере Джерри Коффина, это не обязательно тот случай, когда for_each является лучшим вариантом, но цикл for не является единственной альтернативой.)

1
ответ дан 18 авг. '13 в 11:24
источник

В основном вы должны выполнить итерацию по всей коллекции. Поэтому я предлагаю вам написать собственный вариант for_each(), используя только 2 параметра. Это позволит вам переписать пример Терри Махаффи следующим образом:

for_each(container, [](int& i) {
    i += 10;
});

Я думаю, что это действительно более читаемо, чем цикл for. Однако для этого требуются расширения компилятора С++ 0x.

0
ответ дан 12 янв. '10 в 12:05
источник