Czy są jakieś zalety std::for_each
over for
loop? std::for_each
Wydaje mi się, że tylko utrudnia czytelność kodu. Dlaczego więc niektóre standardy kodowania zalecają jego użycie?
c++
stl
foreach
coding-style
missingfaktor
źródło
źródło
std::for_each
gdy jest używany zboost.lambda
lubboost.bind
często może poprawić czytelnośćOdpowiedzi:
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
Albo to
gdy dostępna jest składnia pętli oparta
for
na zakresie :Ten rodzaj składni jest już dostępny w Javie i C # już od jakiegoś czasu i tak naprawdę jest o wiele więcej
foreach
pętli niż klasycznychfor
pętli w każdym ostatnio widzianym kodzie Java lub C #.źródło
Element & e
gdyauto & e
(lubauto const &e
) wygląda lepiej. UżyłbymElement 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 naElement
.Oto kilka powodów:
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.)
Pozwala na napisanie algorytmu na podstawie for_each, który działa z dowolnym iteratorem.
Zmniejsza ryzyko głupich błędów podczas pisania.
Otwiera również swój umysł do reszty STL algorytmów, jak
find_if
,sort
,replace
itp i nie będą wyglądać tak dziwny anymore. To może być ogromna wygrana.Aktualizacja 1:
Co najważniejsze, pomaga wyjść poza
for_each
pę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):
Lub z lambdami C ++ x11:
czy IMO jest bardziej czytelny niż:
Również to (lub z lambdami, zobacz inne):
Jest bardziej zwięzły niż:
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?
źródło
outer_class::inner_class::iterator
lub są to argumenty szablonowe:typename std::vector<T>::iterator
... sam konstrukt for może sam w sobiefor_each
w drugim przykładzie jest niepoprawne (powinno byćfor_each( bananas.begin(), bananas.end(),...
for_each
jest 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_each
bez konieczności aktualizowania kodu iteracji. Musisz wziąć pod uwagę, że na świecie są inne kontenery niżstd::vector
zwykłe stare tablice języka C, aby zobaczyć zaletyfor_each
.Główną wadą programu
for_each
jest to, że wymaga funktora, więc składnia jest niezgrabna. Zostało to naprawione w C ++ 11 (dawniej C ++ 0x) dzięki wprowadzeniu lambd:To nie będzie dla ciebie dziwne za 3 lata.
źródło
for ( int v : int_vector ) {
(nawet jeśli można to dzisiaj zasymulować za pomocą BOOST_FOREACH)std::for_each(container, [](int& i){ ... });
. Chodzi mi o to, dlaczego trzeba pisać kontener dwa razy?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.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 / skomplikowaneboost::lambda
s), znajdujęBOOST_FOREACH
i C ++ 0x zakres w oparciu o jaśniejsze:vs
źródło
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_each
itslef jest implementowana jako pętlawięc do Ciebie należy wybór tego, co jest dla Ciebie odpowiednie.
źródło
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 :
źródło
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żnafor
pę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, żestd::for_each()
wymusza przejście przez cały zakres.W większości masz rację: przez większość czasu
std::for_each
jest to strata netto. Chciałbym iść tak daleko, by porównaćfor_each
dogoto
.goto
zapewnia 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 widzeniegoto
w 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 wgoto
ostateczności.Wśród standardowych algorytmów
for_each
jest bardzo podobnie - można go użyć do zaimplementowania praktycznie wszystkiego, co oznacza, że widzenie niefor_each
mówi praktycznie nic o tym, do czego jest używane w tej sytuacji. Niestety, stosunek ludzi do ludzifor_each
dotyczy tego, gdzie był ich stosunek dogoto
(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życiefor_each
. Kończą się czymś takim:A ich stanowisko jest prośbą o jakiej kombinacji
bind1st
,mem_fun
itp Muszą zrobić coś takiego: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:
i użyj
std::copy
:To działa - i nie wymaga praktycznie żadnej pracy, aby dowiedzieć się, że drukuje zawartość
coll
dostd::cout
.źródło
boost::mem_fn(&XXX::print)
raczej niżXXX::print
std::cout
jako argument, aby zadziałał).Zaleta pisania funkcjonalnego, ponieważ jest bardziej czytelna, może nie pojawiać się, kiedy
for(...)
ifor_each(...
).Jeśli użyjesz wszystkich algorytmów w function.h, zamiast używać pętli for, kod stanie się znacznie bardziej czytelny;
jest znacznie bardziej czytelny niż;
I to jest to, co uważam za fajne, uogólnij pętle for do funkcji jednej linii =)
źródło
Łatwy:
for_each
jest przydatny, gdy masz już funkcję do obsługi każdego elementu tablicy, więc nie musisz pisać lambdy. Z pewnością tojest lepszy niż
Ponadto
for
pętla dystansowa wykonuje iterację tylko po całych kontenerach od początku do końca, podczas gdyfor_each
jest bardziej elastyczna.źródło
for_each
Pę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.
Zauważ, że jeśli chcesz wykonać określoną operację na każdym obiekcie, możesz użyć
std::mem_fn
lubboost::bind
(std::bind
w następnym standardzie), lubboost::lambda
(lambdy w następnym standardzie), aby to uprościć: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_each
pę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:
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:
Nawet jeśli w przypadku
for_each
pojawi się konkretny konstrukt, który uczyni go bardziej naturalnym:Mam tendencję do mieszania
for_each
konstrukcji 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 ) )
), wybieramfor_each
konstrukcję, 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ął)źródło
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:
do:
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:
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.
źródło
Kiedyś nie lubiłem
std::for_each
i 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_each
Algorytm 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 zstd::for_each
, a zatem prawdopodobnie byłaby łatwiejsza do zrozumienia.Funktory dla z
std::for_each
większym prawdopodobieństwem będą bardziej ogólne, a tym samym wielokrotnego użytku, np .: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_each
niż 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
,transform
etc.źródło
for
jest dla pętli, która może iterować każdy element lub co trzeci itd.for_each
służy do iteracji tylko każdego elementu. Wynika to z jego nazwy. Więc jest bardziej jasne, co zamierzasz zrobić w swoim kodzie.źródło
++
. Może nietypowe, ale tak samo jest z pętlą for.transform
aby kogoś nie zmylić.Jeśli często używasz innych algorytmów z STL, jest kilka zalet
for_each
:W przeciwieństwie do tradycyjnej pętli for,
for_each
wymusza napisanie kodu, który będzie działał dla każdego iteratora wejściowego. Takie ograniczenie może być naprawdę dobre, ponieważ:for_each
.Używanie
for_each
czasami 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; niekonieczniefor_each
jest to najlepsza opcja, ale pętla for nie jest jedyną alternatywą).źródło
Możesz pisać dzięki C ++ 11 i dwóm prostym szablonom
jako zamiennik
for_each
lub 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_each
zawsze 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ą
źródło
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:
Myślę, że jest to rzeczywiście bardziej czytelne niż pętla for. Wymaga to jednak rozszerzeń kompilatora C ++ 0x.
źródło
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ć
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.
źródło
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/
źródło
for_each
robi, w tym przypadku, to nie ma odpowiedzi na pytanie o jego zaletach.Pętla może pęknąć; Nie chcę być papugą dla Herba Suttera, więc tutaj jest link do jego prezentacji: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Przeczytaj też komentarze :)
źródło
for_each
pozwalają nam na implementację wzorca Fork-Join . Poza tym obsługuje płynny interfejs .wzór łączenia rozwidlonego
Możemy dodać implementację,
gpu::for_each
aby używać cuda / gpu do obliczeń heterogeniczno-równoległych, wywołując zadanie lambda w wielu procesach roboczych.I
gpu::for_each
moż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.
źródło