Существует ли какое - либо преимущество std::for_each
над for
циклом? Мне 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 # в течение некоторого времени, и на самом деле в каждом недавнем коде Java или C #, который я видел, имеется гораздо больше foreach
циклов, чем классических for
циклов.
Element & e
если auto & e
(или auto const &e
) выглядит лучше. Я бы использовал Element const e
(без ссылки), когда я хочу неявное преобразование, скажем, когда источник представляет собой коллекцию разных типов, и я хочу, чтобы они преобразовывались в Element
.
Вот несколько причин:
Кажется, это затрудняет читабельность только потому, что вы к этому не привыкли и / или не используете правильные инструменты, чтобы сделать его действительно простым. (см. boost :: range и boost :: bind / boost :: lambda для помощников. Многие из них войдут в C ++ 0x и сделают for_each и связанные функции более полезными.)
Это позволяет вам написать алгоритм поверх for_each, который работает с любым итератором.
Это уменьшает вероятность глупых ошибок ввода.
Она также открывает свой ум к остальной части STL-алгоритмов, как find_if
, sort
, replace
и т.д. , и это не будет выглядеть так странно больше. Это может быть огромной победой.
Обновление 1:
Самое главное, это поможет вам выйти за рамки for_each
цикла for, как будто это все, что есть, и посмотреть на другие STL-файлы, такие как find / sort / partition / copy_replace_if, параллельное выполнение ... или что-то еще.
Большая обработка может быть написана очень кратко, используя «остальную часть» братьев и сестер 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?
outer_class::inner_class::iterator
или они являются шаблонными аргументами: typename std::vector<T>::iterator
... сама конструкция for может натолкнуться на
for_each
во втором примере неверно (должно бытьfor_each( bananas.begin(), bananas.end(),...
for_each
является более общим. Вы можете использовать его для перебора любого типа контейнера (передавая итераторы начала / конца). Вы можете поменять контейнеры под функцией, которая for_each
не требует обновления кода итерации. Вам нужно учитывать, что в мире есть и другие контейнеры, кроме std::vector
обычных C-массивов, чтобы увидеть преимущества for_each
.
Основным недостатком for_each
является то, что требуется функтор, поэтому синтаксис неуклюжий. Это исправлено в C ++ 11 (ранее C ++ 0x) с введением лямбд:
std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});
Это не будет выглядеть странно для вас через 3 года.
for ( int v : int_vector ) {
(даже если сегодня его можно смоделировать с помощью BOOST_FOREACH)
std::for_each(container, [](int& i){ ... });
. Я имею в виду, почему один вынужден писать контейнер дважды?
container.each { ... }
без упоминания начальных и конечных итераторов. Я считаю излишним, что мне приходится все время указывать конечный итератор.
Лично, в любое время, когда мне нужно было бы выйти из-под контроля 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)));
Это очень субъективно, некоторые скажут, что использование for_each
сделает код более читабельным, так как позволяет обрабатывать разные коллекции с одинаковыми соглашениями.
for_each
его реализация реализована в виде цикла
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
for ( ; first!=last; ++first ) f(*first);
return f;
}
так что вам решать, что подходит именно вам.
Как и многие функции алгоритма, первоначальная реакция заключается в том, что думать, что использовать foreach более нечитаемо, чем цикл. Это была тема многих огненных войн.
Как только вы привыкнете к идиоме, вы можете найти ее полезной. Одно очевидное преимущество заключается в том, что он заставляет кодировщика отделить внутреннее содержимое цикла от фактической функциональности итерации. (Хорошо, я думаю, что это преимущество. Другие говорят, что вы просто нарезаете код без реальной выгоды).
Еще одно преимущество заключается в том, что когда я вижу foreach, я знаю, что либо каждый элемент будет обработан, либо будет сгенерировано исключение.
Цикл for позволяет несколько вариантов завершения цикла. Вы можете позволить циклу пройти полный курс, или вы можете использовать ключевое слово 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;
}
}
}
std::for_each()
в старом стандарте (который использовался во время публикации) вы должны использовать именованный функтор, который, как вы говорите, обеспечивает удобочитаемость и запрещает преждевременное прерывание цикла. Но тогда эквивалентный for
цикл не имеет ничего, кроме вызова функции, и это также запрещает преждевременное прерывание. Но кроме этого , я думаю , что вы сделали отличную точку, говоря , что std::for_each()
навязывает , проходящие через весь диапазон.
Вы в основном правы: большую часть времени 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
.
boost::mem_fn(&XXX::print)
а неXXX::print
std::cout
качестве аргумента, чтобы он работал).
Преимущество написания функционала для того, чтобы быть более читабельным, может не проявляться, когда 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 в одну строку функций =)
Легко: for_each
полезно, когда у вас уже есть функция для обработки каждого элемента массива, поэтому вам не нужно писать лямбду. Конечно, это
for_each(a.begin(), a.end(), a_item_handler);
лучше, чем
for(auto& item: a) {
a_item_handler(a);
}
Кроме того, ранжированный for
цикл перебирает только целые контейнеры от начала до конца, в то время for_each
как более гибок.
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 (сотрудник вовлек меня в это)
Помимо читабельности и производительности, одним из аспектов, который обычно упускается из виду, является последовательность. Существует много способов реализовать цикл 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 или C ++ 0x? Лично я предпочел бы беспокоиться об этом, чем о том, как реализовать или прочитать случайный цикл for / while.
Я привык не любить std::for_each
и думал, что без лямбды это было сделано совершенно неправильно. Однако некоторое время назад я передумал, и теперь мне это действительно нравится. И я думаю, что это даже улучшает читабельность и облегчает тестирование вашего кода способом TDD.
std::for_each
Алгоритм может быть прочитан как сделать кое - что со всеми элементами в диапазоне , которые могут улучшить читаемость. Скажем, действие, которое вы хотите выполнить, имеет длину 20 строк, а функция, где выполняется действие, также имеет длину около 20 строк. Это сделало бы функцию длиной в 40 строк с обычным циклом for и только около 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, чем явный цикл.
Все эти функторы обычно легче пройти в модульных тестах, чем в явном цикле for в середине длинной функции, и это само по себе уже большая победа для меня.
std::for_each
также, как правило, более надежный, так как вы с меньшей вероятностью ошибетесь с дальностью.
И, наконец, компилятор может генерировать немного лучший код, std::for_each
чем для определенных типов созданного вручную цикла for, так как он (for_each) всегда выглядит одинаково для компилятора, и разработчики компилятора могут использовать все свои знания, чтобы сделать его настолько хорошим, насколько они жестяная банка.
То же относится и к другим стандартным алгоритмам, таким как find_if
и transform
т. Д.
for
предназначен для цикла, который может повторять каждый элемент или каждый третий и т. д. for_each
предназначен для повторения только каждого элемента. Это ясно из его названия. Так что более понятно, что вы собираетесь делать в своем коде.
++
. Может быть, необычно, но цикл for делает то же самое.
transform
чтобы не путать кого-либо.
Если вы часто используете другие алгоритмы из STL, есть несколько преимуществ for_each
:
В отличие от традиционного цикла for, for_each
вы вынуждены писать код, который будет работать для любого входного итератора. Ограничение таким образом может быть хорошей вещью, потому что:
for_each
.Использование for_each
иногда делает более очевидным, что вы можете использовать более специфическую функцию STL, чтобы сделать то же самое. (Как и в примере с Джерри Коффином; это не обязательно for_each
лучший вариант, но цикл for - не единственная альтернатива.)
С C ++ 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); }
В основном вам придется перебирать всю коллекцию . Поэтому я предлагаю вам написать свой собственный вариант for_each (), принимая только 2 параметра. Это позволит вам переписать пример Терри Махаффи следующим образом:
for_each(container, [](int& i) {
i += 10;
});
Я думаю, что это действительно более читабельно, чем цикл. Однако для этого требуются расширения компилятора C ++ 0x.
Я считаю, что for_each плохо для читабельности. Идея хорошая, но с ++ очень трудно писать для чтения, по крайней мере, для меня. c ++ 0x лямда-выражения помогут. Мне очень нравится идея лямда. Однако, на первый взгляд, я думаю, что синтаксис очень уродливый, и я не уверен на 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.
Конечно, случаи меняются, это как раз то, что я обычно нахожу лучше всего.
Итератор может быть вызовом функции, которая выполняется на каждой итерации цикла.
Смотрите здесь: http://www.cplusplus.com/reference/algorithm/for_each/
for_each
делает, и в этом случае он не отвечает на вопрос о его преимуществах.
Для цикла может сломаться; Я не хочу быть попугаем для Херба Саттера, поэтому вот ссылка на его презентацию: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Обязательно прочитайте также комментарии :)
for_each
Позвольте нам реализовать шаблон Fork-Join . Помимо этого он поддерживает Fluent-интерфейс .
Мы можем добавить реализацию 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);
std::for_each
при использовании сboost.lambda
илиboost.bind
часто может улучшить читаемость