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

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

Хорошая вещь с 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 диапазона :

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

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

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

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

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

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

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

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

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

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

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

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

Или с C ++ x11 lambdas:

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

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

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

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

 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, которые работают с диапазонами вместо пары iteratorов. boost :: range_ex, после выпуска, будет включать это, и, возможно, он будет там и в C ++ 0x?

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

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

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

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

Лично, в любое время, когда мне понадобится использовать std::for_each (напишите специальные функции / сложный boost::lambda s), я нахожу 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))); 

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

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

так что вам решать, что вам нужно.

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

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

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

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

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

 for(std::vector::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::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip) { if(ip->IsBroken()) { return false; } } } 

Вы в основном правы: большую часть времени std::for_each – это чистый убыток. Я бы for_each так далеко, чтобы сравнить for_each с goto . goto обеспечивает наиболее универсальное управление streamом – вы можете использовать его для реализации практически любой другой структуры управления, которую вы можете себе представить. Однако эта универсальность означает, что видение 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 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(std::cout, "\n")); 

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

Преимущество написания функционала для более читаемого, может не отображаться, когда 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 на одну строку функций =)

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

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

Это лучше чем

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

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

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

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

 struct myfunctor { void operator()( int arg1 ) { code } }; void apply( std::vector 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 const & v ) { // code std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) ); // code } 

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

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

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

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

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

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

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

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

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

 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); 

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

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

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

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

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

 struct DeleteElement { template  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 и т. Д.

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

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

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

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

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

Шаблоны:

 template 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 range_ range(iter const &begin, iter const &end) { return range_(begin,end); } 

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

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

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

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

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

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

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

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

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

См. Здесь: http://www.cplusplus.com/reference/algorithm/for_each/

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

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

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

Для петли можно сломать; Я не хочу быть попугаем для Херба Саттера, так вот ссылка на его презентацию: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Обязательно прочитайте также комментарии 🙂

for_each позволяют нам использовать шаблон вилки-присоединения . Кроме того, он поддерживает свободный интерфейс .

комбинация fork-join

Мы можем добавить реализацию gpu::for_each для использования cuda / gpu для гетерогенных параллельных вычислений, вызвав lambda-задачу у нескольких работников.

 gpu::for_each(users.begin(),users.end(),update_summary); // all summary is complete now // go access the user-summary here. 

И gpu::for_each может дождаться завершения работы всех lambda-задач перед выполнением следующих операторов.

свободно-интерфейс

Это позволяет нам писать человеко-читаемый код в сжатой форме.

 accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year)); std::for_each(accounts.begin(),accounts.end(),mark_dormant); 
  • Написание собственного контейнера STL
  • std :: transform () и toupper (), нет соответствующей функции
  • Можно ли наследовать реализацию из контейнеров STL, а не делегировать?
  • Является ли размер std :: array определенным стандартом
  • Как объявить вектор атома в C ++
  • Ли вектор :: erase () на векторе указателей объектов уничтожает сам объект?
  • Как использовать std :: sort с вектором структур и функцией сравнения?
  • Краткое описание запрограммированных правил свертывания ссылок: (1) A & & -> A &, (2) A & && -> A &, (3) A && & -> A & и (4) A && && -> A &&
  • Расширение стандартной библиотеки C ++ по наследству?
  • Почему «! =» Используется с iteratorами вместо «<»?
  • Спецификация шаблона Функция перегрузки функций VS
  • Давайте будем гением компьютера.