Zalety std :: for_each w porównaniu z pętlą for


168

Czy są jakieś zalety std::for_eachover forloop? std::for_eachWydaje mi się, że tylko utrudnia czytelność kodu. Dlaczego więc niektóre standardy kodowania zalecają jego użycie?


10
std::for_eachgdy jest używany z boost.lambdalub boost.bindczęsto może poprawić czytelność

Pytanie i zaakceptowana odpowiedź pochodzą z 2010 r. Aby uzyskać bardziej aktualną odpowiedź (od 2018 r.), Zobacz tutaj: fluentcpp.com/2018/03/30/is-stdfor_each-obsolete
Erel Segal-Halevi

Odpowiedzi:


181

Fajną rzeczą w C ++ 11 (wcześniej nazywanym C ++ 0x) jest to, że ta męcząca debata zostanie rozstrzygnięta.

Chodzi mi o to, że nikt przy zdrowych zmysłach, który chce powtórzyć całą kolekcję, nadal tego nie wykorzysta

for(auto it = collection.begin(); it != collection.end() ; ++it)
{
   foo(*it);
}

Albo to

for_each(collection.begin(), collection.end(), [](Element& e)
{
   foo(e);
});

gdy dostępna jest składnia pętli opartafor na zakresie :

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

Ten rodzaj składni jest już dostępny w Javie i C # już od jakiegoś czasu i tak naprawdę jest o wiele więcej foreachpętli niż klasycznych forpętli w każdym ostatnio widzianym kodzie Java lub C #.


12
Właściwie pętla foreach z czerpakiem była dostępna przez długi czas w trybie boost, a ja nadal chcę iterować z for_each i funkcją lambda.
Viktor Sehr

19
Założenie, że chcemy mieć cały zakres kontenerów, nie jest częścią pytania, więc jest to tylko częściowa odpowiedź.
Adrian McCarthy

6
Zwróć uwagę, że pętla po elemencie prawdopodobnie nie jest jedyną rzeczą, którą chcesz zrobić, więc dobrym pomysłem może być użycie for_each tylko po to, aby dowiedzieć się o find / partition / copy_replace_if i innych, co jest tak naprawdę dużo w przypadku pętli robić.
Macke

10
Zakres-for jest fajny, z wyjątkiem sytuacji, gdy faktycznie potrzebujesz iteratora (wtedy nie ma sposobu, aby do niego dotrzeć).
Damon

5
Nie używałbym nawet, Element & egdy auto & e(lub auto const &e) wygląda lepiej. Użyłbym Element const e(bez odniesienia), gdy chcę niejawnej konwersji, powiedzmy, gdy źródło jest zbiorem różnych typów i chcę, aby zostały przekonwertowane na Element.
Nawaz

54

Oto kilka powodów:

  1. Wydaje się, że utrudnia to czytelność tylko dlatego, że nie jesteś do tego przyzwyczajony i / lub nie używasz odpowiednich narzędzi, aby to naprawdę ułatwić. (zobacz pomoce w boost :: range i boost :: bind / boost :: lambda. Wiele z nich przejdzie do C ++ 0x i sprawi, że for_each i powiązane funkcje będą bardziej użyteczne.)

  2. Pozwala na napisanie algorytmu na podstawie for_each, który działa z dowolnym iteratorem.

  3. Zmniejsza ryzyko głupich błędów podczas pisania.

  4. Otwiera również swój umysł do reszty STL algorytmów, jak find_if, sort, replaceitp i nie będą wyglądać tak dziwny anymore. To może być ogromna wygrana.

Aktualizacja 1:

Co najważniejsze, pomaga wyjść poza for_eachpętle for, tak jak to wszystko, i przyjrzeć się innym alogom STL, takim jak find / sort / partition / copy_replace_if, wykonywanie równoległe ... lub cokolwiek innego.

Wiele operacji przetwarzania można napisać bardzo zwięźle, używając „reszty” rodzeństwa for_each, ale jeśli wszystko, co zrobisz, to napisanie pętli for z różnymi wewnętrznymi logikami, to nigdy nie nauczysz się ich używać. kończy się wymyślaniem koła w kółko.

I (wkrótce dostępny styl asortymentu for_each):

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

Lub z lambdami C ++ x11:

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

czy IMO jest bardziej czytelny niż:

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

Również to (lub z lambdami, zobacz inne):

for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));

Jest bardziej zwięzły niż:

for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
    my_monkey->eat(*i);
} 

Zwłaszcza jeśli masz kilka funkcji do wywołania w kolejności ... ale może to tylko ja. ;)

Aktualizacja 2 : Napisałem własne, jednowierszowe opakowania stl-algos, które działają z zakresami zamiast parami iteratorów. boost :: range_ex, po wydaniu, będzie zawierał to i może będzie tam również w C ++ 0x?


+1, kilka funkcji lub typów zagnieżdżonych: outer_class::inner_class::iteratorlub są to argumenty szablonowe: typename std::vector<T>::iterator... sam konstrukt for może sam w sobie
napotkać

4
(btw: for_eachw drugim przykładzie jest niepoprawne (powinno byćfor_each( bananas.begin(), bananas.end(),...
David Rodríguez - dribeas

Napisałem opakowania, które używają zakresów zamiast dwóch iteratorów. Będą one dostępne później (zobacz range_ex), ale i tak każdy powinien je mieć. (Dodano aktualizację na ten temat.)
Macke

1
Obsługa równoległego przetwarzania to nie. 1 powód tutaj. Możemy dodać implementację, aby używać cuda / gpu do obliczeń heterogeniczno-równoległych.
shuva

25

for_eachjest bardziej ogólny. Możesz go użyć do iteracji po dowolnym typie kontenera (przekazując iteratory początku / końca). Możesz potencjalnie zamienić kontenery pod funkcją, która używa, for_eachbez konieczności aktualizowania kodu iteracji. Musisz wziąć pod uwagę, że na świecie są inne kontenery niż std::vectorzwykłe stare tablice języka C, aby zobaczyć zalety for_each.

Główną wadą programu for_eachjest to, że wymaga funktora, więc składnia jest niezgrabna. Zostało to naprawione w C ++ 11 (dawniej C ++ 0x) dzięki wprowadzeniu lambd:

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

To nie będzie dla ciebie dziwne za 3 lata.


3
+1. Reklama, gdy for_each przyjmuje kontener / zakres zamiast dwóch iteratorów, jest jeszcze bardziej niesamowita.
Macke,

2
@Marcus: to będzie konstrukcja dystansowa, a składnia sama w sobie nie odczyta „for_each”: for ( int v : int_vector ) {(nawet jeśli można to dzisiaj zasymulować za pomocą BOOST_FOREACH)
David Rodríguez - dribeas

@David: Mam na myśli ogólne dodanie funkcji opartych na zakresach (więc możesz używać zakresów ze wszystkimi funkcjami for_each, copy, remove_if itp. Itp.),
Macke

1
Dlaczego nie jest możliwe, aby napisać: std::for_each(container, [](int& i){ ... });. Chodzi mi o to, dlaczego trzeba pisać kontener dwa razy?
Giorgio

1
@freitass: Zapisanie kontenera raz, tak jak w moim poprzednim komentarzu, mogłoby domyślnie używać iteratora begin end bez jawnego wywoływania go. Większość języków udostępniających funkcje wyższego rzędu na kolekcjach (Ruby, Scala, ...) pisze coś takiego, container.each { ... }nie wspominając o iteratorach początku i końca. Wydaje mi się trochę zbędne, że muszę cały czas określać iterator końcowy.
Giorgio

18

Osobiście za każdym razem, gdy musiałbym zrobić wszystko, co w mojej mocy, aby użyć std::for_each(pisać funktory specjalnego przeznaczenia / skomplikowane boost::lambdas), znajduję BOOST_FOREACHi C ++ 0x zakres w oparciu o jaśniejsze:

BOOST_FOREACH(Monster* m, monsters) {
     if (m->has_plan()) 
         m->act();
}

vs

std::for_each(monsters.begin(), monsters.end(), 
  if_then(bind(&Monster::has_plan, _1), 
    bind(&Monster::act, _1)));

12

Jest to bardzo subiektywne, niektórzy powiedzą, że użycie for_each uczyni kod bardziej czytelnym, ponieważ pozwala traktować różne kolekcje według tych samych konwencji. for_eachitslef jest implementowana jako pętla

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

więc do Ciebie należy wybór tego, co jest dla Ciebie odpowiednie.


11

Podobnie jak w przypadku wielu funkcji algorytmu, początkową reakcją jest myślenie, że użycie foreach jest bardziej nieczytelne niż użycie pętli. To był temat wielu wojen z płomieniami.

Gdy już przyzwyczaisz się do idiomu, może się on okazać przydatny. Jedną z oczywistych zalet jest to, że zmusza koder do oddzielenia wewnętrznej zawartości pętli od rzeczywistej funkcjonalności iteracji. (OK, myślę, że to zaleta. Inni twierdzą, że po prostu kroisz kod bez żadnych korzyści).

Inną zaletą jest to, że kiedy widzę foreach, wiem, że albo każdy element zostanie przetworzony, albo zostanie wyrzucony wyjątek.

Do pętli umożliwia kilka opcji zakończenia pętli. Możesz pozwolić pętli na pełny przebieg lub możesz użyć słowa kluczowego break, aby jawnie wyskoczyć z pętli, lub użyć słowa kluczowego return, aby opuścić całą funkcję w połowie pętli. W przeciwieństwie do tego, foreach nie zezwala na te opcje, a to sprawia, że ​​jest bardziej czytelny. Wystarczy spojrzeć na nazwę funkcji i poznać pełną naturę iteracji.

Oto przykład mylącej pętli 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;
      }
   }
}

1
Masz rację, ale twój przykład motywacji nie jest do końca sprawiedliwy. Kiedy używasz std::for_each()starego standardu (z czasów tego postu), musisz użyć nazwanego funktora, który zachęca do czytelności, jak mówisz, i zabrania przedwczesnego wyjścia z pętli. Ale wtedy równoważna forpętla ma tylko wywołanie funkcji, a to również uniemożliwia przedwczesne przerwanie. Ale poza tym myślę, że miałeś doskonały argument, mówiąc, że std::for_each() wymusza przejście przez cały zakres.
wilhelmtell

10

W większości masz rację: przez większość czasu std::for_eachjest to strata netto. Chciałbym iść tak daleko, by porównać for_eachdo goto. gotozapewnia najbardziej wszechstronną możliwą kontrolę przepływu - można jej użyć do wdrożenia praktycznie każdej innej struktury sterowania, jaką można sobie wyobrazić. Ta bardzo wszechstronność oznacza jednak, że widzenie gotow izolacji nie mówi praktycznie nic o tym, co ma robić w tej sytuacji. W rezultacie prawie nikt przy zdrowych zmysłach nie używa ich w gotoostateczności.

Wśród standardowych algorytmów for_eachjest bardzo podobnie - można go użyć do zaimplementowania praktycznie wszystkiego, co oznacza, że ​​widzenie nie for_eachmówi praktycznie nic o tym, do czego jest używane w tej sytuacji. Niestety, stosunek ludzi do ludzi for_eachdotyczy tego, gdzie był ich stosunek do goto(powiedzmy) 1970 r. Lub później - kilka osób przyłapało na tym, że powinno się go używać tylko w ostateczności, ale wielu nadal uważa to za podstawowy algorytm, i rzadko, jeśli w ogóle, używaj innych. W większości przypadków nawet szybki rzut oka ujawniłby, że jedna z alternatyw jest zdecydowanie lepsza.

Na przykład, jestem prawie pewien, że straciłem orientację, ile razy widziałem ludzi piszących kod do drukowania zawartości kolekcji za pomocą for_each. Na podstawie postów, które widziałem, może to być najczęstsze użycie for_each. Kończą się czymś takim:

class XXX { 
// ...
public:
     std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};

A ich stanowisko jest prośbą o jakiej kombinacji bind1st, mem_funitp Muszą zrobić coś takiego:

std::vector<XXX> coll;

std::for_each(coll.begin(), coll.end(), XXX::print);

pracy i wydrukuj elementy coll. Gdyby naprawdę działał dokładnie tak, jak go tam napisałem, byłby mierny, ale tak nie jest - a zanim zaczniesz działać, trudno jest znaleźć te kilka fragmentów kodu związanych z tym, co jest dzieje się wśród elementów, które ją trzymają.

Na szczęście jest znacznie lepszy sposób. Dodaj normalne przeciążenie modułu wprowadzającego strumień dla XXX:

std::ostream &operator<<(std::ostream *os, XXX const &x) { 
   return x.print(os);
}

i użyj std::copy:

std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));

To działa - i nie wymaga praktycznie żadnej pracy, aby dowiedzieć się, że drukuje zawartość colldo std::cout.


+1, jest jednak jeden błąd. W pierwszym przykładzie powinno być boost::mem_fn(&XXX::print)raczej niżXXX::print
missingfaktor

Dlatego powiedziałem, że ten przykład nie zadziała, a oni piszą z prośbą o pomoc, aby to zadziałało (och, a także musisz powiązać std::coutjako argument, aby zadziałał).
Jerry Coffin

1
Chociaż ogólnie jest to dobra odpowiedź, pytanie nie dotyczy wartości for_each ani jej wartości w porównaniu z innymi algorytmami standardowymi, ale jego wartości w porównaniu z pętlą for. Możesz o tym pomyśleć w przypadkach, gdy nie ma zastosowania żaden inny algorytm standardu. Czy użyłbyś wtedy pętli for_each czy for? Pomyśl o tym i cokolwiek wymyślisz, to powinna być twoja odpowiedź.
Christian Rau

@ChristianRau: Zawsze istnieje cienka granica między odpowiedzią na pytanie dokładnie tak, jak zostało zadane, a próbą podania przydatnych informacji. Bezpośrednia odpowiedź na dokładnie zadane przez niego pytania brzmiałaby: „Prawdopodobnie nie. Kto wie?”, Ale byłaby zbyt bezużyteczna, by warto było się tym przejmować. Jednocześnie zbytnie oddalanie się (np. Polecanie Haskella zamiast któregokolwiek z powyższych) również nie będzie zbyt przydatne.
Jerry Coffin

3
@ChristianRau: Jak sądzisz, że „... w większości przypadków std :: for_each to strata netto” nie odpowiada na pytanie, czy std :: for_each zapewnia przewagę?
Jerry Coffin

8

Zaleta pisania funkcjonalnego, ponieważ jest bardziej czytelna, może nie pojawiać się, kiedy for(...)i for_each(...).

Jeśli użyjesz wszystkich algorytmów w function.h, zamiast używać pętli for, kod stanie się znacznie bardziej czytelny;

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

jest znacznie bardziej czytelny niż;

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

I to jest to, co uważam za fajne, uogólnij pętle for do funkcji jednej linii =)


6

Łatwy: for_eachjest przydatny, gdy masz już funkcję do obsługi każdego elementu tablicy, więc nie musisz pisać lambdy. Z pewnością to

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

jest lepszy niż

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

Ponadto forpętla dystansowa wykonuje iterację tylko po całych kontenerach od początku do końca, podczas gdy for_eachjest bardziej elastyczna.


4

for_eachPętla ma ukryć iteratory (szczegół jak pętla jest realizowana) z kodem użytkownika i określić jasne semantykę operacji: każdy element będzie powtórzyć dokładnie raz.

Problem z czytelnością w obecnym standardzie polega na tym, że wymaga on funktora jako ostatniego argumentu zamiast bloku kodu, więc w wielu przypadkach trzeba dla niego napisać określony typ funktora. To zmienia się w mniej czytelny kod, ponieważ obiekty funktora nie mogą być definiowane w miejscu (lokalne klasy zdefiniowane w funkcji nie mogą być używane jako argumenty szablonu), a implementacja pętli musi zostać odsunięta od rzeczywistej pętli.

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
}

Zauważ, że jeśli chcesz wykonać określoną operację na każdym obiekcie, możesz użyć std::mem_fnlub boost::bind( std::bindw następnym standardzie), lub boost::lambda(lambdy w następnym standardzie), aby to uprościć:

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

Co jest nie mniej czytelne i bardziej kompaktowe niż wersja ręcznie rozwijana, jeśli masz funkcję / metodę do wywołania. Implementacja może zapewnić inne implementacje for_eachpętli (pomyśl o przetwarzaniu równoległym).

Nadchodzący standard na różne sposoby zajmie się niektórymi niedociągnięciami, pozwoli na lokalnie zdefiniowane klasy jako argumenty do szablonów:

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

Poprawa lokalizacji kodu: kiedy przeglądasz, widzisz, co robi właśnie tam. Właściwie nie musisz nawet używać składni klasy do definiowania funktora, ale użyj tam lambdy:

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

Nawet jeśli w przypadku for_eachpojawi się konkretny konstrukt, który uczyni go bardziej naturalnym:

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

Mam tendencję do mieszania for_eachkonstrukcji z ręcznie zwijanymi pętlami. Gdy tylko wywołanie istniejącej funkcji lub metody jest tym, czego potrzebuję ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )), wybieram for_eachkonstrukcję, która odbiera kodowi wiele rzeczy z iteratorem płyty kotłowej. Kiedy potrzebuję czegoś bardziej złożonego i nie mogę zaimplementować funktora tylko kilka linii powyżej rzeczywistego użycia, zwijam własną pętlę (utrzymuje operację w miejscu). W niekrytycznych sekcjach kodu mogę iść z BOOST_FOREACH (współpracownik mnie w to wciągnął)


3

Oprócz czytelności i wydajności, często pomijanym aspektem jest spójność. Istnieje wiele sposobów implementacji pętli for (lub while) na iteratorach, z:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
    do_something(*iter);
}

do:

C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
    do_something(*iter);
    ++iter;
}

z wieloma przykładami na różnych poziomach wydajności i potencjału błędów.

Jednak użycie for_each wymusza spójność poprzez oderwanie pętli:

for_each(c.begin(), c.end(), do_something);

Jedyne, o co musisz się teraz martwić, to: czy implementujesz ciało pętli jako funkcję, funktor lub lambdę za pomocą funkcji Boost lub C ++ 0x? Osobiście wolałbym się tym bardziej martwić niż implementować lub czytać losową pętlę for / while.


3

Kiedyś nie lubiłem std::for_eachi myślałem, że bez lambdy jest to całkowicie złe. Jednak jakiś czas temu zmieniłem zdanie i teraz naprawdę to kocham. Myślę, że nawet poprawia czytelność i ułatwia testowanie kodu w trybie TDD.

std::for_eachAlgorytm może być odczytywane jako coś zrobić ze wszystkimi elementami w zakresie , który może poprawić czytelność. Powiedzmy, że czynność, którą chcesz wykonać, ma długość 20 linii, a funkcja, w której jest wykonywana, ma również około 20 linii. To sprawiłoby, że funkcja miałaby długość 40 linii z konwencjonalną pętlą for i tylko około 20 z std::for_each, a zatem prawdopodobnie byłaby łatwiejsza do zrozumienia.

Funktory dla z std::for_eachwiększym prawdopodobieństwem będą bardziej ogólne, a tym samym wielokrotnego użytku, np .:

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

W kodzie miałbyś tylko jedną linijkę, std::for_each(v.begin(), v.end(), DeleteElement())która jest nieco lepsza IMO niż jawna pętla.

Wszystkie te funktory są zwykle łatwiejsze do wykonania w testach jednostkowych niż jawna pętla for w środku długiej funkcji, a to już dla mnie duża wygrana.

std::for_each jest również ogólnie bardziej niezawodny, ponieważ jest mniej prawdopodobne, że pomylisz się z zakresem.

I wreszcie, kompilator może wygenerować nieco lepszy kod std::for_eachniż dla niektórych typów ręcznie tworzonej pętli for, ponieważ (for_each) zawsze wygląda tak samo dla kompilatora, a twórcy kompilatora mogą włożyć całą swoją wiedzę, aby była tak dobra, jak oni mogą.

To samo dotyczy innych algorytmów, takich jak std find_if, transformetc.


2

forjest dla pętli, która może iterować każdy element lub co trzeci itd. for_eachsłuży do iteracji tylko każdego elementu. Wynika to z jego nazwy. Więc jest bardziej jasne, co zamierzasz zrobić w swoim kodzie.


4
nie, jeśli przekażesz mu iterator, który zwiększa się o 3 z każdym ++. Może nietypowe, ale tak samo jest z pętlą for.
Potatoswatter

W takim razie może lepiej będzie, transformaby kogoś nie zmylić.
Kirill V. Lyadvinsky

2

Jeśli często używasz innych algorytmów z STL, jest kilka zalet for_each:

  1. Często będzie prostszy i mniej podatny na błędy niż pętla for, częściowo dlatego, że będziesz przyzwyczajony do funkcji z tym interfejsem, a częściowo dlatego, że w wielu przypadkach jest on nieco bardziej zwięzły.
  2. Chociaż pętla for oparta na zakresie może być jeszcze prostsza, jest mniej elastyczna (jak zauważył Adrian McCarthy, iteruje po całym kontenerze).
  3. W przeciwieństwie do tradycyjnej pętli for, for_eachwymusza napisanie kodu, który będzie działał dla każdego iteratora wejściowego. Takie ograniczenie może być naprawdę dobre, ponieważ:

    1. W rzeczywistości może być konieczne późniejsze dostosowanie kodu do pracy z innym kontenerem.
    2. Na początku może Cię to czegoś nauczyć i / lub zmienić Twoje nawyki na lepsze.
    3. Nawet jeśli zawsze pisałbyś dla pętli, które są idealnie równoważne, inne osoby, które modyfikują ten sam kod, mogą tego nie zrobić bez pytania o ich użycie for_each.
  4. Używanie for_eachczasami sprawia, że ​​bardziej oczywiste jest, że możesz użyć bardziej specyficznej funkcji STL, aby zrobić to samo. (Jak w przykładzie Jerry'ego Coffina; niekoniecznie for_eachjest to najlepsza opcja, ale pętla for nie jest jedyną alternatywą).


2

Możesz pisać dzięki C ++ 11 i dwóm prostym szablonom

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

jako zamiennik for_eachlub pętla. Dlaczego warto to wybrać, sprowadza się do zwięzłości i bezpieczeństwa, nie ma szans na błąd w wyrażeniu, którego nie ma.

Dla mnie for_eachzawsze był lepszy na tych samych podstawach, kiedy ciało pętli jest już funktorem i wykorzystam każdą możliwą korzyść.

Nadal używasz trzech wyrażeń for, ale teraz, kiedy widzisz jedno, wiesz, że jest w nim coś do zrozumienia, nie jest to szablonowe. Ja nienawidzę boilerplate. Żałuję jego istnienia. To nie jest prawdziwy kod, nie ma czego się nauczyć czytając go, to jeszcze jedna rzecz, którą trzeba sprawdzić. Wysiłek umysłowy można zmierzyć na podstawie tego, jak łatwo jest zardzewieć przy sprawdzaniu.

Szablony są

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

1

Przeważnie będziesz musiał iterować po całej kolekcji . Dlatego proponuję napisać własny wariant for_each (), biorąc tylko 2 parametry. Umożliwi to przepisanie przykładu Terry'ego Mahaffeya jako:

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

Myślę, że jest to rzeczywiście bardziej czytelne niż pętla for. Wymaga to jednak rozszerzeń kompilatora C ++ 0x.


1

Uważam, że for_each jest zły pod względem czytelności. Pomysł jest dobry, ale c ++ bardzo utrudnia pisanie do odczytu, przynajmniej dla mnie. Pomocne będą wyrażenia lamda c ++ 0x. Bardzo podoba mi się pomysł lamd. Jednak na pierwszy rzut oka wydaje mi się, że składnia jest bardzo brzydka i nie jestem w 100% pewien, czy kiedykolwiek się do niej przyzwyczaię. Może za 5 lat przyzwyczaię się do tego i nie będę się nad tym zastanawiać, ale może nie. Czas pokaże :)

Wolę używać

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

Uważam, że wyraźna pętla for jest bardziej przejrzysta do odczytania, a jawność przy użyciu nazwanych zmiennych dla iteratorów początkowych i końcowych zmniejsza bałagan w pętli for.

Oczywiście przypadki są różne, to jest właśnie to, co zwykle uważam za najlepsze.


0

Iterator może być wywołaniem funkcji wykonywanej w każdej iteracji w pętli.

Zobacz tutaj: http://www.cplusplus.com/reference/algorithm/for_each/


2
Posty zawierające tylko linki nie dają dobrych odpowiedzi, a poza tym, gdzie w tym linku widać coś, co przypomina wywoływalny iterator? Jestem prawie pewien, że ta koncepcja po prostu nie ma sensu. Może po prostu podsumowując to, co for_eachrobi, w tym przypadku, to nie ma odpowiedzi na pytanie o jego zaletach.
underscore_d


0

for_eachpozwalają nam na implementację wzorca Fork-Join . Poza tym obsługuje płynny interfejs .

wzór łączenia rozwidlonego

Możemy dodać implementację, gpu::for_eachaby używać cuda / gpu do obliczeń heterogeniczno-równoległych, wywołując zadanie lambda w wielu procesach roboczych.

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

I gpu::for_eachmoże poczekać, aż pracownicy wykonają wszystkie zadania lambda, zanim wykonają następne instrukcje.

płynny interfejs

Pozwala nam w zwięzły sposób napisać kod czytelny dla człowieka.

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.