Преимущества std:: for_each для цикла - программирование

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

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

4b9b3361

Ответ 1

Хорошая вещь с С++ 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 или С#, который я видел.

Ответ 2

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

  1. Кажется, это затрудняет читабельность только потому, что вы к этому не привыкли и/или не используете правильные инструменты, чтобы сделать его действительно простым. (см. boost :: range и boost :: bind/boost :: lambda для помощников. Многие из них войдут в С++ 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));

Или с С++ 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 будет включать его, и, возможно, он будет и в С++ 0x?

Ответ 3

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 года.

Ответ 4

Лично, в любое время, когда мне понадобится использовать 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)));

Ответ 5

его очень субъективно, некоторые скажут, что использование 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;
  }

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

Ответ 6

Как и многие функции алгоритма, первоначальная реакция заключается в том, чтобы считать его более нечитаемым, чтобы использовать 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;
      }
   }
}

Ответ 7

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

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

Ответ 9

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

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

лучше, чем

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

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

Ответ 10

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

Ответ 11

Помимо читаемости и производительности, один из аспектов, который обычно игнорируется, - это последовательность. Существует много способов реализовать цикл 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.

Ответ 12

Я не любил 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 и т.д.

Ответ 13

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

Ответ 14

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

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

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

Ответ 15

С С++ 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); }

Ответ 16

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

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

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

Ответ 17

Я нахожу 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.

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

Ответ 19

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

Ответ 20

for_each позволяет нам реализовать шаблон Fork-Join. Помимо этого он поддерживает свободный интерфейс.

шаблон вилочного соединения

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

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

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

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

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

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);