Ci sono dei vantaggi std::for_each
dell'over for
loop? Per me, std::for_each
sembra solo ostacolare la leggibilità del codice. Perché allora alcuni standard di codifica ne raccomandano l'uso?
Ci sono dei vantaggi std::for_each
dell'over for
loop? Per me, std::for_each
sembra solo ostacolare la leggibilità del codice. Perché allora alcuni standard di codifica ne raccomandano l'uso?
Risposte:
La cosa bella di C ++ 11 (precedentemente chiamato C ++ 0x), è che questo noioso dibattito si risolverà.
Voglio dire, nessuno nella loro mente giusta, che vuole iterare su un'intera collezione, continuerà a usarlo
for(auto it = collection.begin(); it != collection.end() ; ++it)
{
foo(*it);
}
O questo
for_each(collection.begin(), collection.end(), [](Element& e)
{
foo(e);
});
quando è disponibile la sintassi del loop basata su intervallofor
:
for(Element& e : collection)
{
foo(e);
}
Questo tipo di sintassi è disponibile da tempo in Java e C #, e in realtà ci sono molti più foreach
loop rispetto ai for
loop classici in ogni recente codice Java o C # che ho visto.
Element & e
come auto & e
(o auto const &e
) sembra migliore. Userei Element const e
(senza riferimento) quando voglio una conversione implicita, ad esempio quando l'origine è una raccolta di diversi tipi e voglio che si convertano Element
.
Ecco alcuni motivi:
Sembra ostacolare la leggibilità solo perché non ci sei abituato e / o non usi gli strumenti giusti per renderlo davvero facile. (vedi boost :: range e boost :: bind / boost :: lambda per gli helper. Molti di questi andranno in C ++ 0x e renderanno for_each e le funzioni correlate più utili.)
Ti permette di scrivere un algoritmo sopra for_each che funziona con qualsiasi iteratore.
Riduce la possibilità di stupidi bug di battitura.
Si apre anche la vostra mente per il resto dei STL-algoritmi, come find_if
, sort
, replace
, ecc e questi non sarà più così strano. Questa può essere una grande vittoria.
Aggiornamento 1:
for_each
Ancora più importante, ti aiuta ad andare oltre rispetto ai cicli for come è tutto ciò che c'è, e guardare gli altri alog STL, come find / sort / partition / copy_replace_if, esecuzione parallela ... o qualsiasi altra cosa.
Molte elaborazioni possono essere scritte in modo molto conciso usando "il resto" dei fratelli di for_each, ma se tutto ciò che fai è scrivere un for-loop con varie logiche interne, allora non imparerai mai come usarli e finiscono per inventare la ruota ancora e ancora.
E (il prossimo range-style disponibile per_each):
for_each(monsters, boost::mem_fn(&Monster::think));
O con lambda C ++ x11:
for_each(monsters, [](Monster& m) { m.think(); });
l'IMO è più leggibile di:
for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) {
i->think();
}
Anche questo (o con lambda, vedi gli altri):
for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));
È più conciso di:
for(Bananas::iterator i = bananas.begin(); i != bananas.end(); ++i) {
my_monkey->eat(*i);
}
Soprattutto se hai diverse funzioni da chiamare in ordine ... ma forse sono solo io. ;)
Aggiornamento 2 : ho scritto i miei wrapper one-liner di stl-algos che funzionano con intervalli anziché con coppie di iteratori. boost :: range_ex, una volta rilasciato, lo includerà e forse sarà presente anche in C ++ 0x?
outer_class::inner_class::iterator
oppure sono argomenti modello: typename std::vector<T>::iterator
... il costrutto stesso può incorrere in un costrutto a più righe in sé
for_each
secondo esempio non è corretto (dovrebbe esserefor_each( bananas.begin(), bananas.end(),...
for_each
è più generico. Puoi usarlo per iterare su qualsiasi tipo di contenitore (passando negli iteratori inizio / fine). È possibile scambiare potenzialmente contenitori sotto una funzione che utilizza for_each
senza dover aggiornare il codice di iterazione. È necessario considerare che esistono altri contenitori al mondo oltre alle std::vector
semplici vecchie matrici C per vedere i vantaggi di for_each
.
Il principale svantaggio di for_each
è che richiede un functor, quindi la sintassi è goffa. Questo problema è stato risolto in C ++ 11 (precedentemente C ++ 0x) con l'introduzione di lambdas:
std::vector<int> container;
...
std::for_each(container.begin(), container.end(), [](int& i){
i+= 10;
});
Questo non ti sembrerà strano per 3 anni.
for ( int v : int_vector ) {
(anche se può essere simulato oggi con BOOST_FOREACH)
std::for_each(container, [](int& i){ ... });
. Voglio dire, perché si è costretti a scrivere due volte container?
container.each { ... }
senza menzionare gli iteratori di inizio e fine. Trovo un po 'ridondante che devo specificare sempre l'iteratore finale.
Personalmente, ogni volta che dovrei andare fuori dal mio modo di usare std::for_each
(scrivere funzioni speciali / complicati boost::lambda
), trovo BOOST_FOREACH
e la gamma basata su C ++ 0x per chiarezza:
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)));
è molto soggettivo, alcuni diranno che l'utilizzo for_each
renderà il codice più leggibile, poiché consente di trattare diverse raccolte con le stesse convenzioni.
for_each
itslef è implementato come un ciclo
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f)
{
for ( ; first!=last; ++first ) f(*first);
return f;
}
quindi sta a te scegliere ciò che è giusto per te.
Come molte delle funzioni dell'algoritmo, una reazione iniziale è pensare che sia più illeggibile usare foreach che un loop. È stato argomento di molte guerre di fiamma.
Una volta che ti sei abituato al linguaggio potresti trovarlo utile. Un ovvio vantaggio è che forza il programmatore a separare il contenuto interno del loop dall'effettiva funzionalità di iterazione. (OK, penso che sia un vantaggio. Altri dicono che stai solo tagliando il codice senza un vero vantaggio).
Un altro vantaggio è che quando vedo foreach, so che o ogni articolo verrà elaborato o verrà generata un'eccezione.
Un ciclo for consente diverse opzioni per terminare il loop. Puoi lasciare che il ciclo esegua il suo corso completo oppure puoi usare la parola chiave break per saltare esplicitamente fuori dal ciclo o usare la parola chiave return per uscire dall'intera funzione a metà ciclo. Al contrario, foreach non consente queste opzioni e questo lo rende più leggibile. Puoi semplicemente dare un'occhiata al nome della funzione e conoscere la natura completa dell'iterazione.
Ecco un esempio di confusione per il ciclo:
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()
il vecchio standard (quello del tempo di questo post) devi usare un nominativo funzione, che incoraggia la leggibilità come dici tu e proibisce di uscire prematuramente dal ciclo. Ma poi il for
ciclo equivalente non ha nient'altro che una chiamata di funzione, e anche questo proibisce di scoppiare prematuramente. Ma a parte questo, penso che tu abbia fatto un ottimo punto nel dire che le std::for_each()
forze attraversano l'intera gamma.
Per lo più hai ragione: il più delle volte, std::for_each
è una perdita netta. Mi piacerebbe andare così lontano da confrontare for_each
a goto
. goto
fornisce il controllo di flusso più versatile possibile: puoi utilizzarlo per implementare praticamente qualsiasi altra struttura di controllo che puoi immaginare. Quella versatilità, tuttavia, significa che vedere un goto
isolamento non ti dice praticamente nulla di ciò che è destinato a fare in questa situazione. Di conseguenza, quasi nessuno nella loro mente corretta usa goto
tranne come ultima risorsa.
Tra gli algoritmi standard, for_each
è più o meno allo stesso modo: può essere utilizzato per implementare praticamente qualsiasi cosa, il che significa che vedere non for_each
ti dice praticamente nulla su ciò per cui viene utilizzato in questa situazione. Sfortunatamente, l'atteggiamento della gente nei confronti for_each
di dove era il loro atteggiamento verso goto
(diciamo) nel 1970 circa - alcune persone avevano capito che doveva essere usato solo come ultima risorsa, ma molti lo considerano ancora l'algoritmo primario, e raramente se mai ne usi un altro. La stragrande maggioranza delle volte, anche una rapida occhiata rivelerebbe che una delle alternative era drasticamente superiore.
Ad esempio, sono abbastanza sicuro di aver perso la traccia di quante volte ho visto persone scrivere codice per stampare il contenuto di una raccolta usando for_each
. Sulla base dei post che ho visto, questo potrebbe essere l'uso più comune di for_each
. Finiscono con qualcosa del tipo:
class XXX {
// ...
public:
std::ostream &print(std::ostream &os) { return os << "my data\n"; }
};
E il loro messaggio chiede su quale combinazione di bind1st
, mem_fun
ecc hanno bisogno di fare qualcosa di simile:
std::vector<XXX> coll;
std::for_each(coll.begin(), coll.end(), XXX::print);
lavorare e stampare gli elementi di coll
. Se funzionasse davvero esattamente come l'ho scritto lì, sarebbe mediocre, ma non funziona - e quando lo farai funzionare, è difficile trovare quei pochi frammenti di codice relativi a ciò che è succedendo tra i pezzi che lo tengono insieme.
Fortunatamente, c'è un modo molto migliore. Aggiungi un sovraccarico dell'inseritore di flusso normale per XXX:
std::ostream &operator<<(std::ostream *os, XXX const &x) {
return x.print(os);
}
e usa std::copy
:
std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));
Che fa il lavoro - e prende praticamente senza lavoro a tutti per capire che esso stampa il contenuto di coll
a std::cout
.
boost::mem_fn(&XXX::print)
piuttosto cheXXX::print
std::cout
come argomento per farlo funzionare).
Il vantaggio di scrivere funzionale per essere più leggibile, potrebbe non apparire quando for(...)
e for_each(...
).
Se si utilizzano tutti gli algoritmi in funzionali.h, invece di usare for-loops, il codice diventa molto più leggibile;
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);
è molto più leggibile di;
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);
}
Ed è quello che penso sia così bello, generalizzare i for-loop in una riga di funzioni =)
Facile: for_each
è utile quando hai già una funzione per gestire ogni elemento dell'array, quindi non devi scrivere un lambda. Certamente questo
for_each(a.begin(), a.end(), a_item_handler);
è meglio di
for(auto& item: a) {
a_item_handler(a);
}
Inoltre, il for
ciclo a distanza scorre solo su interi contenitori dall'inizio alla fine, mentre for_each
è più flessibile.
Il for_each
ciclo ha lo scopo di nascondere gli iteratori (dettaglio di come viene implementato un ciclo) dal codice utente e definire una semantica chiara sull'operazione: ogni elemento verrà ripetuto esattamente una volta.
Il problema con la leggibilità nello standard attuale è che richiede un funzione come ultimo argomento anziché un blocco di codice, quindi in molti casi è necessario scrivere un tipo di funzione specifico per esso. Ciò si trasforma in codice meno leggibile poiché gli oggetti functor non possono essere definiti sul posto (le classi locali definite all'interno di una funzione non possono essere utilizzate come argomenti del modello) e l'implementazione del ciclo deve essere allontanata dal ciclo effettivo.
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
}
Si noti che se si desidera eseguire un'operazione specifica su ciascun oggetto, è possibile utilizzare std::mem_fn
, oppure boost::bind
( std::bind
nello standard successivo) o boost::lambda
(lambdas nello standard successivo) per semplificare:
void function( int value );
void apply( std::vector<X> const & v ) {
// code
std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) );
// code
}
Che non è meno leggibile e più compatto della versione arrotolata a mano se si dispone di una funzione / metodo da chiamare in atto. L'implementazione potrebbe fornire altre implementazioni del for_each
ciclo (si pensi all'elaborazione parallela).
Lo standard imminente si occupa di alcune carenze in diversi modi, consentirà le classi definite localmente come argomenti per i modelli:
void apply( std::vector<int> const & v ) {
// code
struct myfunctor {
void operator()( int ) { code }
};
std::for_each( v.begin(), v.end(), myfunctor() );
// code
}
Migliorare la località del codice: quando navighi vedi cosa sta facendo proprio lì. È un dato di fatto, non è nemmeno necessario utilizzare la sintassi della classe per definire il functor, ma usare un lambda proprio lì:
void apply( std::vector<int> const & v ) {
// code
std::for_each( v.begin(), v.end(),
[]( int ) { // code } );
// code
}
Anche se per il caso for_each
ci sarà un costrutto specifico che lo renderà più naturale:
void apply( std::vector<int> const & v ) {
// code
for ( int i : v ) {
// code
}
// code
}
Tendo a mescolare il for_each
costrutto con anelli arrotolati a mano. Quando mi serve solo una chiamata a una funzione o un metodo esistente ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )
) vado per il for_each
costrutto che toglie al codice molte cose iteratore sulla piastra della caldaia. Quando ho bisogno di qualcosa di più complesso e non riesco a implementare un functor solo un paio di righe sopra l'uso effettivo, faccio scorrere il mio loop (mantiene l'operazione in atto). In sezioni di codice non critiche potrei andare con BOOST_FOREACH (un collaboratore mi ha coinvolto)
A parte la leggibilità e le prestazioni, un aspetto comunemente trascurato è la coerenza. Esistono molti modi per implementare un ciclo for (o while) sugli iteratori, da:
for (C::iterator iter = c.begin(); iter != c.end(); iter++) {
do_something(*iter);
}
per:
C::iterator iter = c.begin();
C::iterator end = c.end();
while (iter != end) {
do_something(*iter);
++iter;
}
con molti esempi in mezzo a vari livelli di efficienza e potenziale bug.
L'uso di for_each, tuttavia, rafforza la coerenza sottraendo il ciclo:
for_each(c.begin(), c.end(), do_something);
L'unica cosa di cui ti devi preoccupare ora è: implementi il corpo del loop come funzione, funzione o lambda usando le funzionalità Boost o C ++ 0x? Personalmente, preferirei preoccuparmi di questo piuttosto che come implementare o leggere un ciclo casuale per / mentre.
Non mi piaceva std::for_each
e pensavo che senza lambda fosse stato completamente sbagliato. Tuttavia ho cambiato idea qualche tempo fa, e ora lo adoro davvero. E penso che migliora anche la leggibilità e semplifica il test del codice in modo TDD.
L' std::for_each
algoritmo può essere letto come fare qualcosa con tutti gli elementi nel raggio d'azione , il che può migliorare la leggibilità. Dire che l'azione che si desidera eseguire è lunga 20 righe e anche la funzione in cui viene eseguita l'azione è lunga circa 20 righe. Ciò renderebbe una funzione lunga 40 righe con un convenzionale per loop, e solo circa 20 con std::for_each
, quindi probabilmente più facile da comprendere.
È std::for_each
più probabile che siano più generici e quindi riutilizzabili, ad esempio:
struct DeleteElement
{
template <typename T>
void operator()(const T *ptr)
{
delete ptr;
}
};
E nel codice avresti solo un one-liner come quello std::for_each(v.begin(), v.end(), DeleteElement())
che è leggermente meglio IMO di un loop esplicito.
Tutti questi funzioni sono normalmente più facili da ottenere sotto test unitari di un esplicito ciclo nel mezzo di una lunga funzione, e questo da solo è già una grande vittoria per me.
std::for_each
è anche generalmente più affidabile, poiché è meno probabile che tu commetta un errore con la portata.
E, infine, il compilatore potrebbe produrre un codice leggermente migliore std::for_each
rispetto a certi tipi di cicli fatti a mano, poiché (for_each) sembra sempre lo stesso per il compilatore e gli autori del compilatore possono mettere tutte le loro conoscenze, per renderlo buono come loro può.
Lo stesso vale per altri algoritmi std come find_if
, transform
ecc.
for
è per loop che può iterare ogni elemento o ogni terzo ecc. for_each
è per iterare solo ogni elemento. È chiaro dal suo nome. Quindi è più chiaro cosa intendi fare nel tuo codice.
++
. Forse insolito, ma lo è anche un for-loop.
transform
per non confondere qualcuno.
Se usi frequentemente altri algoritmi dell'STL, ci sono diversi vantaggi per for_each
:
A differenza di un ciclo tradizionale, for_each
ti costringe a scrivere codice che funzionerà per qualsiasi iteratore di input. Essere limitati in questo modo può effettivamente essere una buona cosa perché:
for_each
.L'uso a for_each
volte rende più ovvio che è possibile utilizzare una funzione STL più specifica per fare la stessa cosa. (Come nell'esempio di Jerry Coffin; non è necessariamente il caso che for_each
sia l'opzione migliore, ma un ciclo for non è l'unica alternativa.)
Con C ++ 11 e due modelli semplici, puoi scrivere
for ( auto x: range(v1+4,v1+6) ) {
x*=2;
cout<< x <<' ';
}
in sostituzione for_each
o in loop. Perché scegliere si riduce a brevità e sicurezza, non c'è possibilità di errore in un'espressione che non c'è.
Per me, for_each
è sempre stato meglio per gli stessi motivi quando il corpo del loop è già un functor e ne trarrò vantaggio.
Usi ancora l'espressione a tre for
, ma ora quando ne vedi una sai che c'è qualcosa da capire lì, non è una piastra di comando. Io odio il boilerplate. Mi risento per la sua esistenza. Non è un vero codice, non c'è nulla da imparare leggendolo, è solo un'altra cosa che deve essere verificata. Lo sforzo mentale può essere misurato da quanto sia facile essere arrugginiti nel controllarlo.
I modelli sono
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); }
Principalmente dovrai ripetere l'intera collezione . Pertanto ti consiglio di scrivere la tua variante for_each (), prendendo solo 2 parametri. Questo ti permetterà di riscrivere l'esempio di Terry Mahaffey come:
for_each(container, [](int& i) {
i += 10;
});
Penso che questo sia davvero più leggibile di un ciclo for. Tuttavia, ciò richiede le estensioni del compilatore C ++ 0x.
Trovo che for_each sia dannoso per la leggibilità. Il concetto è valido ma c ++ rende molto difficile scrivere leggibile, almeno per me. Le espressioni lamda c ++ 0x aiuteranno. Mi piace molto l'idea dei lama. Tuttavia a prima vista penso che la sintassi sia molto brutta e non sono sicuro al 100% che mi abituerò mai. Forse tra 5 anni mi ci sono abituato e non ci ripenso, ma forse no. Il tempo lo dirà :)
Preferisco usare
vector<thing>::iterator istart = container.begin();
vector<thing>::iterator iend = container.end();
for(vector<thing>::iterator i = istart; i != iend; ++i) {
// Do stuff
}
Trovo esplicito che il loop sia più chiaro da leggere e che esplicitamente usando variabili nominate per gli iteratori di inizio e fine riduce il disordine nel ciclo for.
Naturalmente i casi variano, questo è quello che di solito trovo meglio.
Puoi far sì che l'iteratore sia una chiamata a una funzione che viene eseguita su ogni iterazione attraverso il ciclo.
Vedi qui: http://www.cplusplus.com/reference/algorithm/for_each/
for_each
fa, nel qual caso non risponde alla domanda sui suoi vantaggi.
Perché il loop può interrompersi; Non voglio essere un pappagallo per Herb Sutter, quindi ecco il link alla sua presentazione: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Assicurati di leggere anche i commenti :)
for_each
ci permettono di implementare il modello Fork-Join . Oltre a ciò, supporta l' interfaccia fluente .
È possibile aggiungere l'implementazione gpu::for_each
per utilizzare cuda / gpu per l'elaborazione eterogenea parallela chiamando l'attività lambda in più lavoratori.
gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.
E gpu::for_each
può aspettare che i lavoratori lavorino su tutte le attività lambda per finire prima di eseguire le dichiarazioni successive.
Ci consente di scrivere codice leggibile dall'uomo in modo conciso.
accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
std::for_each
se usato conboost.lambda
oboost.bind
può spesso migliorare la leggibilità