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?
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?
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
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 foreach
pętli niż klasycznych for
pętli w każdym ostatnio widzianym kodzie Java lub C #.
Element & e
gdy 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
.
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):
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?
outer_class::inner_class::iterator
lub są to argumenty szablonowe: typename std::vector<T>::iterator
... sam konstrukt for może sam w sobie
for_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ć zalety for_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:
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.
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 / skomplikowane boost::lambda
s), znajduję BOOST_FOREACH
i 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)));
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ę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.
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;
}
}
}
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 for
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, że std::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
do goto
. 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 widzenie goto
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 w goto
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 nie for_each
mówi praktycznie nic o tym, do czego jest używane w tej sytuacji. Niestety, stosunek ludzi do ludzi for_each
dotyczy 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_fun
itp 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ść coll
do std::cout
.
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(...)
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 =)
Ł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ą to
for_each(a.begin(), a.end(), a_item_handler);
jest lepszy niż
for(auto& item: a) {
a_item_handler(a);
}
Ponadto for
pętla dystansowa wykonuje iterację tylko po całych kontenerach od początku do końca, podczas gdy for_each
jest bardziej elastyczna.
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.
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_fn
lub boost::bind
( std::bind
w 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_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:
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_each
pojawi 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_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 ) )
), wybieram for_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ął)
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.
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 z std::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 .:
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_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.
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.
++
. 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; niekoniecznie for_each
jest to najlepsza opcja, ale pętla for nie jest jedyną alternatywą).
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_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ą
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); }
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.
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.
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/
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 :)
for_each
pozwalają nam na implementację wzorca Fork-Join . Poza tym obsługuje płynny interfejs .
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.
gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.
I gpu::for_each
może poczekać, aż pracownicy wykonają wszystkie zadania lambda, zanim wykonają następne instrukcje.
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);
std::for_each
gdy jest używany zboost.lambda
lubboost.bind
często może poprawić czytelność