Чи є якісь переваги 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 #, і насправді існує більше foreach
циклів, ніж у класичних for
циклів у кожному недавньому Java-коді чи коді C #.
Element & e
як auto & e
(або auto const &e
) виглядає краще. Я б використовував Element const e
(без посилань), коли я хочу неявну конверсію, скажімо, коли джерело - це сукупність різних типів, і я хочу, щоб вони перетворилися в Element
.
Ось кілька причин:
Здається, це перешкоджає читаемості лише тому, що ви до цього не звикли та / або не використовуєте правильних інструментів навколо нього, щоб зробити це справді просто. .
Це дозволяє написати алгоритм поверх for_each, який працює з будь-яким ітератором.
Це знижує ймовірність дурного набору помилок.
Вона також відкриває свій розум до іншої частини STL-алгоритмів, як find_if
, sort
, replace
і т.д. , і це не буде виглядати так дивно більше. Це може бути величезний виграш.
Оновлення 1:
Найголовніше, що це допомагає вам вийти за межі for_each
vs. for-loops, як це все, і подивитися на інші STL-алоги, як-от find / sort / partition / copy_replace_if, паралельне виконання .. або будь-що інше.
Багато обробки можна скласти дуже стисло, використовуючи "решту" братів і сестер for_each, але якщо все, що ви робите, - це написати цикл for-loop з різною внутрішньою логікою, то ви ніколи не навчитеся ними користуватися, і ви врешті-решт винайдіть колесо знову і знову.
І (скоро доступний для діапазону стиль діапазону 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?
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
звичайний масив С, щоб побачити переваги 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
itlef реалізований у вигляді циклу
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
for ( ; first!=last; ++first ) f(*first);
return f;
}
тож саме від вас вирішити, що саме вам підходить.
Як і багато функцій алгоритму, початкова реакція полягає в тому, що вважати, що непередбачуваніше використовувати foreach, ніж цикл. Це було темою багатьох вогняних воєн.
Як тільки ви звикнете до ідіоми, вам це стане корисним. Однією з очевидних переваг є те, що він змушує кодер відокремлювати внутрішній вміст циклу від фактичної функціональності ітерації. (Гаразд, я думаю, що це перевага. Інші кажуть, що ви просто рубаєте код, не маючи справжньої користі).
Ще одна перевага полягає в тому, що коли я бачу передбачення, я знаю, що або кожен предмет буде оброблений, або виняток буде викинуто.
Для контуру дозволяє кілька варіантів завершення циклу. Ви можете дозволити циклу виконувати його повний курс, або ви можете використовувати ключове слово 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(...
).
Якщо ви використовуєте всі алгоритми у funkcional.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_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 (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? Особисто я скоріше переживаю це, ніж те, як реалізувати чи прочитати випадковий цикл для / while.
Раніше я не любив 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
т.д.
for
призначений для циклу, який може повторювати кожен елемент або кожен третій і т.д., for_each
призначений для ітерації лише кожного елемента. З його назви видно. Тож зрозуміліше, що ви маєте намір зробити у своєму коді.
++
. Можливо, незвично, але так само і цикл for-петлі робить те саме.
transform
щоб не плутати когось.
Якщо ви часто використовуєте інші алгоритми STL, є кілька переваг for_each
:
На відміну від традиційного для циклу, 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 (), взявши лише два параметри. Це дозволить вам переписати приклад Террі Махаффі таким чином:
for_each(container, [](int& i) {
i += 10;
});
Я думаю, що це справді читабельніше, ніж для циклу. Однак для цього потрібні розширення компілятора C ++ 0x.
Я вважаю, що for_each погано сприймає читабельність. Концепція хороша, але c ++ дуже важко пише читабельно, принаймні для мене. 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 . Крім того, він підтримує вільний інтерфейс .
Ми можемо додати реалізацію, 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
може часто покращити читабельність