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

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

158
задан missingfaktor 20 October 2012 в 21:08
поделиться

15 ответов

Приятная вещь с 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(Element& e : collection)
{
   foo(e);
}

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

170
ответ дан 23 November 2019 в 21:40
поделиться

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

  1. , кажется, мешает читабельности только потому, что вы не используете это и / или не используете правильные инструменты вокруг него, чтобы сделать это действительно легко. (См. Boost :: Range и Boost :: Bind / Boost :: лямбда для помощников. Многие из них будут переходить на C ++ 0x и сделать For_each и связанные функциями более полезными.)

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

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

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

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

Самое главное, это помогает вам выйти за пределы for_each vs. for-poots, подобные это все, что есть, и посмотрите на другие STL-Alogs, например, найти / сортировать / раздел / copy_replace_if, Параллелл выполнение .. или что угодно.

Многие обработки могут быть написаны очень кратко с использованием «остальных» братьев и сестер For_each, но если все, что вы делаете, это написать на петлю с различной внутренней логикой, вы никогда не узнаете, как их использовать, и Вы закончите изобретать колесо снова и снова.

и (в ближайшее время, доступный диапазон for_each):

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

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

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

IMO более читабелее, чем:

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 тоже?

50
ответ дан 23 November 2019 в 21:40
поделиться

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

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

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

3
ответ дан 23 November 2019 в 21:40
поделиться

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

Смотрите здесь: http://www.cplusplus.com/Reeference/Algorithm/for_cheach/

0
ответ дан 23 November 2019 в 21:40
поделиться

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

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

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

-121--670207-

Feature : Bash, Korn shell (ksh93) и Z shell разрешают подстрочные массивы с переменными со знаком доллара или без него:

array[index]=$value
array[$index]=$value

Это, со знаком доллара, приведет к ожидаемому значению 10000:

unset array
for i in {1..10000}
do
    ((array[$RANDOM%6+1]++))
done
unset total
for count in ${array[@]}
do
    ((total += count))
done
echo $total

Странность : Если удалить знак доллара из

Аналогично, это приводит к 3 вместо 2:

a=1; ((r[a++]++)); echo $a

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

Причина : С помощью знака доллара расширение переменной выполняется до арифметической оценки, поэтому выполняется только один раз. Без знака доллара он выполняется дважды, один раз, чтобы вычислить индекс для поиска и еще раз, чтобы вычислить индекс для назначения (так, фактически, назначение на одном шаге цикла может выглядеть как массив [4] = $ массив [6] + 1 , который полностью скремблирует массив).

-121--1760343-

для - цикл, который может выполнять итерацию каждого элемента или каждого третьего и т.д. , для _ каждый предназначен только для итерации каждого элемента. Из его названия ясно. Так что более ясно, что вы собираетесь делать в своем коде.

2
ответ дан 23 November 2019 в 21:40
поделиться

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

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

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

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

23
ответ дан 23 November 2019 в 21:40
поделиться

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

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

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

1
ответ дан 23 November 2019 в 21:40
поделиться

Вы в основном правильно: большую часть времени STD :: for_each - это чистый убыток. Я бы пошел так далеко, что и сравнить for_each - GOTO . GOTO обеспечивает наиболее универсальный возможный контроль потока - вы можете использовать его для реализации практически любой другой элемент управления, которую вы можете себе представить. Это очень универсальность, однако, означает, что видя Goto в изоляции в изоляции ничего НЕТ о том, что он предназначен для этой ситуации. В результате почти никто не имеет правильного разума GOTO , кроме как в последнем курорте.

Среди стандартных алгоритмов for_each намного одинаково - его можно использовать для реализации практически все, что означает, что видя for_each , как это ничего о том, что это используется для в этой ситуации. К сожалению, отношение людей к for_each о том, где их отношение к было в (сказать) 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);

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

К счастью, есть гораздо лучший способ. Добавьте нормальную перегрузку в потоке в Inserter для 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"));

, который делает работу - и практически не занимает работу, чтобы выяснить, что он печатает содержимое Колл на STD :: Cout .

10
ответ дан 23 November 2019 в 21:40
поделиться

Структура 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 :: лямбда (лямбдас в следующем стандарте), чтобы сделать его проще:

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

, который не менее читабелен и более компактным, чем ручная версия, если вы У вас есть функция / метод, чтобы позвонить на место. Реализация может обеспечить другие реализации LOOP 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 (& Тип :: Обновление, _1)) ) I Перейти к конструкции for_each , которая убирает из кода много вещей итераторной пластины. Когда мне нужно что-то более сложное, и я не могу реализовать функтор всего пару строк выше фактического использования, я катаю свой собственный цикл (сохраняет работу на месте). В не критически важных участках кода я мог бы пойти с Boost_foreach (сотрудник у меня в него в него)

4
ответ дан 23 November 2019 в 21:40
поделиться

Необходимо использовать viewWillAppean: для установки состояния представления до его появления каждый раз, даже если представление уже отображалось ранее. Любое невидимое представление в UITabViewController или UINavigationViewController может быть выгружено в любое время, поэтому нельзя рассчитывать на то, что невидимое представление сохранит свое состояние.

Или для более точного управления реализуйте loadView , viewDidLoad и viewDidUnload .

Если вы поддерживаете собственную иерархию контроллеров ракурсов, вам придется вручную пересылать сообщения viewWillAppean, viewWillansapear и т.д. вашим контроллерам подчиненных ракурсов.

-121--3357904-

Посмотрите на CommonCrypto/CommonDigest.h .

Функция CC _ MD5 (const void * data, CC_LONG len, неподписанный символ * md); вычисляет хеш MD5.

@implementation NSData (MD5)

-(NSString*)md5
{
    unsigned char digest[CC_MD5_DIGEST_LENGTH];
    CC_MD5([self bytes], [self length], digest);

    NSString* s = [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                   digest[0], digest[1], 
                   digest[2], digest[3],
                   digest[4], digest[5],
                   digest[6], digest[7],
                   digest[8], digest[9],
                   digest[10], digest[11],
                   digest[12], digest[13],
                   digest[14], digest[15]];
    return s;

}

@end

При развертывании файлов на сервере можно использовать OpenSSL для вычисления хэшей. Команда openssl md5 filename вычисляет хэш MD5 для файла. Это может быть интегрировано в сценарий.

После загрузки файла приложение вычисляет хэш загруженного файла и сравнивает его с хэшем, сохраненным на сервере.

Очевидно, что если вы хотите обеспечить целостность файла plist, этот plist не может содержать свой собственный хэш.

-121--4435337-

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

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

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

11
ответ дан 23 November 2019 в 21:40
поделиться

Функция : bash, оболочка korn (ksh93) и оболочка z каждая из которых разрешают массивы подписчиков с переменными с или без знака доллара:

array[index]=$value
array[$index]=$value

это, с знаком доллара, будет производить ожидаемое значение 10000 :

unset array
for i in {1..10000}
do
    ((array[$RANDOM%6+1]++))
done
unset total
for count in ${array[@]}
do
    ((total += count))
done
echo $total

Счетность : Если вы удалите знак доллара из случайных, общая сумма будет варьироваться случайным образом, даже более 10000.

аналогично, это производит 3 вместо 2:

a=1; ((r[a++]++)); echo $a

, и вы можете t Используйте знак доллара, потому что это назначение (A - это на LHS), хотя вы могли бы сделать это, если бы вы использовали косвенное управление, но двойная оценка по-прежнему происходит.

Причина : с знаком доллара, переменная расширение выполняется до арифметической оценки, поэтому только один раз выполняется. Без знака доллара он выполняется дважды, один раз, один раз, когда вы рассчитываете индекс для поиска и снова для расчета индекса для назначения (так, по существу, присвоение на один шаг в цикле может выглядеть как Array [4] = $ Array [6] + 1 , которые полностью окрашивают массив).

-121--1760343-

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

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

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

A для цикла позволяет нескольким вариантам для завершения цикла. Вы можете позволить циклу запустить свой полный курс, или вы можете использовать ключевое слово Break , чтобы явно выпрыгнуть из петли или использовать ключевое слово return , чтобы выйти из всей функции Mid-Coot Отказ Напротив, 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
ответ дан 23 November 2019 в 21:40
поделиться

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

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

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

, и это то, что Я думаю, что так приятно, обобщена для петлей к одной линии функции =)

8
ответ дан 23 November 2019 в 21:40
поделиться

Я нахожу for_each, чтобы быть плохим для читабельности. Концепция хорошая, но C ++ очень трудно написать читаемость, по крайней мере, для меня. С ++ 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
}

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

Конечно, случаи варьируются, это только то, что я обычно нахожу лучше всего.

1
ответ дан 23 November 2019 в 21:40
поделиться

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

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

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

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

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

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

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

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

То же самое относится к другим алгоритмам СТД, как FACE_IF , преобразование и т. Д.

3
ответ дан 23 November 2019 в 21:40
поделиться

Лично, в любое время, когда мне нужно будет выйти из моего пути к использованию STD :: for_each (написать специальные функторы / сложные :: лямбда ), Я нахожу BOOST_FOREACK и диапазона 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)));
17
ответ дан 23 November 2019 в 21:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: