Vorteile von std :: for_each gegenüber for loop

165

Gibt es irgendwelche Vorteile von std::for_eachOver- forLoop? Mir std::for_eachscheint nur die Lesbarkeit von Code zu beeinträchtigen. Warum empfehlen dann einige Codierungsstandards die Verwendung?

fehlender Faktor
quelle
10
std::for_eachbei Verwendung mit boost.lambdaoder boost.bindkann oft die Lesbarkeit verbessern
Die Frage und die akzeptierte Antwort stammen aus dem Jahr 2010. Eine aktuellere Antwort (ab 2018) finden Sie hier: fluentcpp.com/2018/03/30/is-stdfor_each-obsolete
Erel Segal-Halevi

Antworten:

181

Das Schöne an C ++ 11 (früher C ++ 0x genannt) ist, dass diese lästige Debatte beigelegt wird.

Ich meine, niemand, der bei klarem Verstand ist und eine ganze Sammlung durchlaufen möchte, wird dies noch verwenden

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

Oder dieses

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

wenn die bereichsbasierte forSchleifensyntax verfügbar ist:

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

Diese Art von Syntax ist seit einiger Zeit in Java und C # verfügbar, und tatsächlich gibt es in jedem neueren Java- oder C # -Code, den ich gesehen habe, weit mehr foreachSchleifen als klassische forSchleifen.

Thomas Petit
quelle
12
Tatsächlich ist eine foreach-Schleife mit scoop schon lange für Boost verfügbar, und ich möchte immer noch mit for_each und einer Lambda-Funktion iterieren.
Viktor Sehr
19
Die Annahme, dass der gesamte Containerbereich gewünscht wird, ist nicht Teil der Frage, daher ist dies nur eine teilweise Antwort.
Adrian McCarthy
6
Beachten Sie, dass das Schleifen über ein Element wahrscheinlich nicht das einzige ist, was Sie tun möchten. Daher kann es eine gute Idee sein, for_each zu verwenden, damit Sie mehr über find / partition / copy_replace_if und die anderen erfahren, was für Schleifen eigentlich eine Menge ist machen.
Macke
10
Range-for ist nett, außer wenn Sie den Iterator tatsächlich benötigen (dann gibt es keine Möglichkeit, dorthin zu gelangen).
Damon
5
Ich würde nicht einmal verwenden, Element & ewie auto & e(oder auto const &e) besser aussieht. Ich würde Element const e(ohne Referenz) verwenden, wenn ich implizite Konvertierung möchte, z. B. wenn die Quelle eine Sammlung verschiedener Typen ist und ich möchte, dass sie in konvertiert werden Element.
Nawaz
52

Hier sind einige Gründe:

  1. Es scheint die Lesbarkeit zu beeinträchtigen, nur weil Sie nicht daran gewöhnt sind und / oder nicht die richtigen Tools verwenden, um es wirklich einfach zu machen. (Weitere Informationen finden Sie unter boost :: range und boost :: bind / boost :: lambda. Viele davon werden in C ++ 0x verwendet und for_each und verwandte Funktionen sind nützlicher.)

  2. Sie können einen Algorithmus über for_each schreiben, der mit jedem Iterator funktioniert.

  3. Es verringert die Wahrscheinlichkeit von dummen Tippfehlern.

  4. Es öffnet auch Ihre Meinung zu dem Rest der STL-Algorithmen, wie find_if, sort, replaceusw. , und diese werden nicht so seltsam aussehen mehr. Dies kann ein großer Gewinn sein.

Update 1:

Am wichtigsten ist, dass es Ihnen hilft, über for_eachFor-for-Schleifen hinauszugehen, wie es alles ist, und sich die anderen STL-Alogs anzusehen, wie find / sort / partition / copy_replace_if, Parallell-Ausführung ... oder was auch immer.

Mit "dem Rest" der Geschwister von for_each kann eine Menge Verarbeitung sehr präzise geschrieben werden. Wenn Sie jedoch nur eine for-Schleife mit verschiedenen internen Logiken schreiben, werden Sie nie lernen, wie man diese verwendet, und Sie werden es tun am Ende immer wieder das Rad erfinden.

Und (der bald verfügbare Range-Style für_each):

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

Oder mit C ++ x11 Lambdas:

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

ist IMO besser lesbar als:

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

Auch dies (oder mit Lambdas, siehe andere):

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

Ist prägnanter als:

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

Vor allem, wenn Sie mehrere Funktionen in der richtigen Reihenfolge aufrufen müssen ... aber vielleicht bin das nur ich. ;)

Update 2 : Ich habe meine eigenen Einzeiler-Wrapper von STL-Algen geschrieben, die mit Bereichen anstelle von Iteratorpaaren arbeiten. boost :: range_ex wird das nach seiner Veröffentlichung enthalten und vielleicht auch in C ++ 0x?

Macke
quelle
+1, mehrere Funktionen oder verschachtelte Typen: outer_class::inner_class::iteratoroder sie sind Vorlagenargumente: typename std::vector<T>::iterator... das for-Konstrukt selbst kann auf ein Konstrukt mit vielen Zeilen an sich
stoßen
4
(Übrigens: Das for_eachim zweiten Beispiel ist falsch (sollte seinfor_each( bananas.begin(), bananas.end(),...
David Rodríguez - Dribeas
Ich habe Wrapper geschrieben, die Bereiche anstelle von zwei Iteratoren verwenden. Diese werden später verfügbar sein (siehe range_ex), aber jeder sollte sie trotzdem haben. (Update dazu hinzugefügt.)
Macke
1
Parallelverarbeitungsunterstützung ist nein. 1 Grund hier. Wir können eine Implementierung hinzufügen, um cuda / gpu für heterogen-paralleles Rechnen zu verwenden.
Shuva
24

for_eachist allgemeiner. Sie können es verwenden, um über jeden Containertyp zu iterieren (indem Sie die Start- / End-Iteratoren übergeben). Sie können möglicherweise Container unter einer Funktion austauschen, die verwendet wird, for_eachohne den Iterationscode aktualisieren zu müssen. Sie müssen berücksichtigen, dass es auf der Welt andere Container als std::vectoreinfache C-Arrays gibt, um die Vorteile von zu erkennen for_each.

Der Hauptnachteil von for_eachist, dass es einen Funktor braucht, so dass die Syntax klobig ist. Dies wird in C ++ 11 (früher C ++ 0x) mit der Einführung von Lambdas behoben:

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

Dies wird in 3 Jahren nicht sonderbar für Sie aussehen.

Terry Mahaffey
quelle
3
+1. Wenn for_each einen Container / Bereich anstelle von zwei Iteratoren verwendet, wird es immer fantastischer.
Macke
2
@Marcus: Das wird das Range-For-Konstrukt sein und die Syntax lautet nicht 'for_each' an sich: for ( int v : int_vector ) {(auch wenn es heute mit BOOST_FOREACH simuliert werden kann)
David Rodríguez - dribeas
@ David: Ich beziehe mich auf die allgemeine Hinzufügung von bereichsbasierten Funktionen (so können Sie Bereiche mit all diesen Funktionen for_each, copy, remove_if usw. usw. verwenden),
Macke
1
Warum ist es nicht möglich zu schreiben : std::for_each(container, [](int& i){ ... });. Ich meine, warum muss man zweimal Container schreiben?
Giorgio
1
@freitass: Das einmalige Schreiben des Containers wie in meinem vorherigen Kommentar könnte standardmäßig den Start-End-Iterator verwenden, ohne ihn explizit aufzurufen. Die meisten Sprachen, die Funktionen höherer Ordnung für Sammlungen bereitstellen (Ruby, Scala, ...), schreiben so etwas wie container.each { ... }ohne Erwähnung von Anfangs- und Enditeratoren. Ich finde es etwas überflüssig, dass ich ständig den Enditerator angeben muss.
Giorgio
17

Persönlich finde ich jedes Mal, wenn ich mir die Mühe machen müsste std::for_each, spezielle Funktionen zu schreiben (spezielle Funktoren / komplizierte boost::lambdas schreiben ), BOOST_FOREACHund C ++ 0x ist bereichsbasiert, um klarer zu werden:

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)));
Onkel Bens
quelle
11

sein sehr subjektiv, werden einige sagen , dass die Verwendung for_each des Code macht mehr lesbar, da es verschiedene Sammlungen mit den gleichen Konventionen zu behandeln ermöglicht. for_eachitslef ist als Schleife implementiert

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

Es liegt also an Ihnen, zu entscheiden, was für Sie richtig ist.

Alon
quelle
11

Wie bei vielen Algorithmusfunktionen besteht eine erste Reaktion darin, zu denken, dass es unlesbarer ist, foreach als eine Schleife zu verwenden. Es war ein Thema vieler Flammenkriege.

Sobald Sie sich an die Redewendung gewöhnt haben, finden Sie sie möglicherweise nützlich. Ein offensichtlicher Vorteil besteht darin, dass der Codierer gezwungen wird, den inneren Inhalt der Schleife von der eigentlichen Iterationsfunktionalität zu trennen. (OK, ich denke, es ist ein Vorteil. Andere sagen, Sie zerhacken nur den Code ohne wirklichen Nutzen).

Ein weiterer Vorteil ist, dass ich weiß, dass entweder jeder Artikel verarbeitet oder eine Ausnahme ausgelöst wird, wenn ich foreach sehe .

Eine for- Schleife bietet verschiedene Möglichkeiten zum Beenden der Schleife. Sie können die Schleife ihren vollen Verlauf nehmen lassen oder das Schlüsselwort break verwenden, um explizit aus der Schleife zu springen, oder das Schlüsselwort return verwenden , um die gesamte Funktion in der Mitte der Schleife zu beenden. Im Gegensatz dazu lässt foreach diese Optionen nicht zu und ist daher besser lesbar. Sie können nur einen Blick auf den Funktionsnamen werfen und kennen die vollständige Art der Iteration.

Hier ist ein Beispiel für eine verwirrende for- Schleife:

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;
      }
   }
}
Andrew Shepherd
quelle
1
Sie machen einen guten Punkt, aber Ihr Motivationsbeispiel ist nicht ganz fair. Wenn Sie std::for_each()den alten Standard (den der Zeit dieses Beitrags) verwenden, müssen Sie einen benannten Funktor verwenden, der die Lesbarkeit fördert, wie Sie sagen, und ein vorzeitiges Ausbrechen der Schleife verhindert. Aber dann hat die äquivalente forSchleife nichts als einen Funktionsaufruf, und auch das verhindert ein vorzeitiges Ausbrechen. Aber abgesehen davon denke ich, dass Sie einen hervorragenden Punkt gemacht haben, indem Sie gesagt haben, dass das Durchlaufen des gesamten Sortiments std::for_each() erzwungen wird.
Wilhelmtell
10

Sie haben größtenteils Recht: Meistens std::for_eachhandelt es sich um einen Nettoverlust. Ich würde sogar so weit gehen zu vergleichen for_eachzu goto. gotobietet die vielseitigste Flusssteuerung, die möglich ist - Sie können damit praktisch jede andere Steuerungsstruktur implementieren, die Sie sich vorstellen können. Diese Vielseitigkeit bedeutet jedoch, dass das gotoisolierte Betrachten praktisch nichts darüber aussagt, was in dieser Situation beabsichtigt ist. Infolgedessen nutzt fast niemand, der bei klarem Verstand ist, gotoaußer als letztes Mittel.

Bei den Standardalgorithmen for_eachist es ähnlich - es kann verwendet werden, um praktisch alles zu implementieren, was bedeutet, dass das Sehen for_eachIhnen praktisch nichts darüber sagt, wofür es in dieser Situation verwendet wird. Leider geht es bei der Einstellung der Menschen for_eachdarum, wo ihre Einstellung zu goto(etwa) 1970 war - einige Leute hatten die Tatsache begriffen, dass es nur als letztes Mittel verwendet werden sollte, aber viele halten es immer noch für den primären Algorithmus, und Verwenden Sie selten oder nie einen anderen. Die überwiegende Mehrheit der Zeit würde sogar ein kurzer Blick zeigen, dass eine der Alternativen drastisch überlegen war.

Ich bin mir zum Beispiel ziemlich sicher, dass ich den Überblick verloren habe, wie oft ich Leute gesehen habe, die Code geschrieben haben, um den Inhalt einer Sammlung mit auszudrucken for_each. Basierend auf Beiträgen, die ich gesehen habe, ist dies möglicherweise die häufigste Verwendung for_each. Sie enden mit so etwas wie:

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

Und ihr Beitrag fragt nach , welche Kombination von bind1st, mem_funusw. müssen sie so etwas wie zu machen:

std::vector<XXX> coll;

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

arbeiten und drucken Sie die Elemente von coll. Wenn es wirklich genau so funktioniert hätte, wie ich es dort geschrieben habe, wäre es mittelmäßig, aber es funktioniert nicht - und wenn Sie es zum Laufen gebracht haben, ist es schwierig, diese wenigen Code-Teile zu finden, die sich auf das beziehen, was mit dem zu tun hat weiter zwischen den Stücken, die es zusammenhalten.

Zum Glück gibt es einen viel besseren Weg. Fügen Sie eine normale Stream Inserter-Überladung für XXX hinzu:

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

und verwenden std::copy:

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

Das funktioniert - und es ist praktisch keine Arbeit erforderlich, um herauszufinden, dass der Inhalt von collto gedruckt wird std::cout.

Jerry Sarg
quelle
+1, es gibt jedoch einen Fehler. Im ersten Beispiel sollte es boost::mem_fn(&XXX::print)eher sein alsXXX::print
fehlender Faktor
Aus diesem Grund habe ich gesagt, dass dieses Beispiel nicht funktioniert, und sie werden um Hilfe gebeten, damit es funktioniert (oh, und Sie müssen auch std::coutals Argument binden, damit es funktioniert).
Jerry Coffin
1
Obwohl es im Allgemeinen eine gute Antwort ist, geht es bei der Frage nicht um den Wert von for_each oder seinen Wert im Vergleich zu anderen Standardalgorithmen, sondern um seinen Wert im Vergleich zu einer for-Schleife. Sie können daran denken, wenn kein anderer Standardalgorithmus angewendet wird. Würden Sie dann eine for_each- oder eine for-Schleife verwenden? Denken Sie darüber nach und was auch immer Sie sich einfallen lassen, das hätte Ihre Antwort sein sollen.
Christian Rau
@ChristianRau: Es gibt immer eine feine Grenze zwischen der genauen Beantwortung der Frage und dem Versuch, nützliche Informationen bereitzustellen. Die direkte Antwort auf genau die Fragen, die er stellte, wäre "Wahrscheinlich nicht. Wer weiß?", Aber sie wäre zu nutzlos, um die Mühe wert zu sein. Gleichzeitig ist es wahrscheinlich auch nicht von großem Nutzen, zu weit weg zu gehen (z. B. Haskell anstelle eines der oben genannten zu empfehlen).
Jerry Coffin
3
@ChristianRau: Wie stellen Sie sich vor, dass "... meistens ist std :: for_each ein Nettoverlust" nicht die Frage beantwortet, ob std :: for_each einen Vorteil bietet?
Jerry Coffin
8

Der Vorteil der Schreibfunktion, um besser lesbar zu sein, zeigt sich möglicherweise nicht, wann for(...)und for_each(...).

Wenn Sie alle Algorithmen in function.h verwenden, anstatt for-Schleifen zu verwenden, wird der Code viel besser lesbar.

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

ist viel besser lesbar als;

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

Und das finde ich so schön, verallgemeinere die for-Schleifen auf einzeilige Funktionen =)

Viktor Sehr
quelle
6

Einfach: for_eachIst nützlich, wenn Sie bereits über eine Funktion verfügen, mit der jedes Array-Element verarbeitet werden kann, sodass Sie kein Lambda schreiben müssen. Sicher das

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

ist besser als

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

Außerdem durchläuft die forFernschleife nur ganze Container von Anfang bis Ende, während for_eachsie flexibler ist.

Tigran Saluev
quelle
4

Die for_eachSchleife soll die Iteratoren (Details zur Implementierung einer Schleife) vor dem Benutzercode verbergen und eine klare Semantik für die Operation definieren: Jedes Element wird genau einmal iteriert.

Das Problem mit der Lesbarkeit im aktuellen Standard besteht darin, dass anstelle eines Codeblocks ein Funktor als letztes Argument erforderlich ist. In vielen Fällen müssen Sie daher einen bestimmten Funktortyp dafür schreiben. Dies führt zu weniger lesbarem Code, da Funktorobjekte nicht direkt definiert werden können (lokale Klassen, die innerhalb einer Funktion definiert sind, können nicht als Vorlagenargumente verwendet werden) und die Implementierung der Schleife von der eigentlichen Schleife entfernt werden muss.

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
}

Beachten Sie, dass Sie, wenn Sie für jedes Objekt eine bestimmte Operation ausführen möchten std::mem_fn, oder boost::bind( std::bindim nächsten Standard) oder boost::lambda(Lambdas im nächsten Standard) verwenden können, um dies zu vereinfachen:

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

Dies ist nicht weniger lesbar und kompakter als die handgerollte Version, wenn Sie über eine Funktion / Methode zum Aufrufen verfügen. Die Implementierung könnte andere Implementierungen der for_eachSchleife bereitstellen (denken Sie an Parallelverarbeitung).

Der kommende Standard behebt einige der Mängel auf unterschiedliche Weise und ermöglicht lokal definierte Klassen als Argumente für Vorlagen:

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

Verbessern der Lokalität von Code: Wenn Sie surfen, sehen Sie, was er genau dort tut. Tatsächlich müssen Sie nicht einmal die Klassensyntax verwenden, um den Funktor zu definieren, sondern verwenden genau dort ein Lambda:

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

Auch wenn es für den Fall von for_eachein spezifisches Konstrukt gibt, das es natürlicher macht:

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

Ich neige dazu, das for_eachKonstrukt mit handgerollten Schleifen zu mischen . Wenn ich nur einen Aufruf einer vorhandenen Funktion oder Methode benötige ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )), greife ich zu dem for_eachKonstrukt, das dem Code eine Menge Kesselplatten-Iterator-Material wegnimmt. Wenn ich etwas Komplexeres brauche und einen Funktor nicht nur ein paar Zeilen über dem tatsächlichen Gebrauch implementieren kann, rolle ich meine eigene Schleife (hält die Operation an Ort und Stelle). In unkritischen Codeabschnitten könnte ich mich für BOOST_FOREACH entscheiden (ein Mitarbeiter hat mich dazu gebracht).

David Rodríguez - Dribeas
quelle
3

Neben der Lesbarkeit und Leistung wird häufig die Konsistenz übersehen. Es gibt viele Möglichkeiten, eine for (oder while) -Schleife über Iteratoren zu implementieren:

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

zu:

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

mit vielen Beispielen dazwischen bei unterschiedlichen Effizienz- und Fehlerpotentialen.

Die Verwendung von for_each erzwingt jedoch die Konsistenz, indem die Schleife entfernt wird:

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

Das einzige, worüber Sie sich jetzt Sorgen machen müssen, ist: Implementieren Sie den Schleifenkörper als Funktion, Funktor oder Lambda mithilfe von Boost- oder C ++ 0x-Funktionen? Persönlich würde ich mir lieber darum kümmern, als eine zufällige for / while-Schleife zu implementieren oder zu lesen.

Jason Govig
quelle
3

Ich mochte es nicht std::for_eachund dachte, dass es ohne Lambda völlig falsch gemacht wurde. Allerdings habe ich es mir vor einiger Zeit anders überlegt und jetzt liebe ich es wirklich. Ich denke, es verbessert sogar die Lesbarkeit und erleichtert das Testen Ihres Codes auf TDD-Weise.

Der std::for_eachAlgorithmus kann mit allen Elementen im Bereich gelesen werden , was die Lesbarkeit verbessern kann . Angenommen, die Aktion, die Sie ausführen möchten, ist 20 Zeilen lang, und die Funktion, in der die Aktion ausgeführt wird, ist ebenfalls etwa 20 Zeilen lang. Das würde eine Funktion mit einer herkömmlichen for-Schleife 40 Zeilen lang und mit einer nur etwa 20 Zeilen lang machen std::for_each, was wahrscheinlich einfacher zu verstehen ist.

Funktoren für std::for_eachsind eher generisch und daher wiederverwendbar, z.

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

Und im Code hätten Sie nur einen Einzeiler, std::for_each(v.begin(), v.end(), DeleteElement())der IMO etwas besser ist als eine explizite Schleife.

Alle diese Funktoren sind normalerweise leichter unter Unit-Tests zu finden als eine explizite for-Schleife mitten in einer langen Funktion, und das allein ist für mich bereits ein großer Gewinn.

std::for_each ist im Allgemeinen auch zuverlässiger, da Sie weniger wahrscheinlich einen Fehler mit der Reichweite machen.

Und schließlich kann der Compiler etwas besseren Code für std::for_eachbestimmte handgefertigte for-Schleifen erzeugen als für bestimmte Arten von handgefertigten for-Schleifen, da er (for_each) für Compiler immer gleich aussieht und Compiler-Autoren ihr gesamtes Wissen einsetzen können, um ihn so gut wie möglich zu machen können.

Gleiches gilt für andere std Algorithmen wie find_if, transformusw.

Dmitry
quelle
2

forist für eine Schleife, die jedes Element oder jedes Drittel usw. iterieren kann. for_eachdient zum Iterieren nur jedes Elements. Es ist klar aus seinem Namen. Es ist also klarer, was Sie in Ihrem Code vorhaben.

Kirill V. Lyadvinsky
quelle
4
nicht, wenn Sie einen Iterator übergeben, der mit jedem um 3 vorrückt ++. Vielleicht ungewöhnlich, aber auch eine For-Schleife macht das Gleiche.
Potatoswatter
In diesem Fall ist es vielleicht besser, transformjemanden nicht zu verwirren.
Kirill V. Lyadvinsky
2

Wenn Sie häufig andere Algorithmen aus der STL verwenden, gibt es mehrere Vorteile for_each:

  1. Es ist oft einfacher und weniger fehleranfällig als eine for-Schleife, teils weil Sie an Funktionen mit dieser Schnittstelle gewöhnt sind, teils weil es in vielen Fällen tatsächlich etwas prägnanter ist.
  2. Obwohl eine bereichsbasierte for-Schleife noch einfacher sein kann, ist sie weniger flexibel (wie von Adrian McCarthy festgestellt, iteriert sie über einen ganzen Container).
  3. Im Gegensatz zu einer herkömmlichen for-Schleife müssen for_eachSie Code schreiben, der für jeden Eingabe-Iterator funktioniert. Auf diese Weise eingeschränkt zu sein, kann tatsächlich eine gute Sache sein, weil:

    1. Möglicherweise müssen Sie den Code tatsächlich anpassen, um später für einen anderen Container zu arbeiten.
    2. Am Anfang könnte es Ihnen etwas beibringen und / oder Ihre Gewohnheiten zum Besseren ändern.
    3. Selbst wenn Sie immer für Schleifen schreiben würden, die vollkommen gleichwertig sind, tun dies andere Personen, die denselben Code ändern, möglicherweise nicht, ohne dazu aufgefordert zu werden for_each.
  4. Die Verwendung for_eachmacht manchmal deutlicher, dass Sie eine spezifischere STL-Funktion verwenden können, um dasselbe zu tun. (Wie in Jerry Coffins Beispiel; es ist nicht unbedingt der Fall, der for_eachdie beste Option ist, aber eine for-Schleife ist nicht die einzige Alternative.)

Sean Patrick Santos
quelle
2

Mit C ++ 11 und zwei einfachen Vorlagen können Sie schreiben

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

als Ersatz für for_eachoder als Schleife. Warum es auf Kürze und Sicherheit ankommt, es besteht keine Möglichkeit eines Fehlers in einem Ausdruck, der nicht vorhanden ist.

Für mich for_eachwar es aus den gleichen Gründen immer besser, wenn der Loop-Body bereits ein Funktor ist, und ich werde jeden Vorteil nutzen, den ich bekommen kann.

Sie verwenden immer noch den Drei-Ausdruck for, aber wenn Sie jetzt einen sehen, wissen Sie, dass es dort etwas zu verstehen gibt, es ist kein Boilerplate. Ich hasse Boilerplate. Ich ärgere mich über seine Existenz. Es ist kein echter Code, es gibt nichts zu lernen, wenn man ihn liest. Es ist nur eine weitere Sache, die überprüft werden muss. Die mentale Anstrengung kann daran gemessen werden, wie leicht es ist, bei der Überprüfung rostig zu werden.

Die Vorlagen sind

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); }
jthill
quelle
1

Meistens müssen Sie die gesamte Sammlung durchlaufen . Daher schlage ich vor, dass Sie Ihre eigene for_each () -Variante schreiben und nur 2 Parameter verwenden. Auf diese Weise können Sie das Beispiel von Terry Mahaffey wie folgt umschreiben :

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

Ich denke, das ist in der Tat besser lesbar als eine for-Schleife. Dies erfordert jedoch die C ++ 0x-Compiler-Erweiterungen.

Dimitri C.
quelle
1

Ich finde for_each schlecht für die Lesbarkeit. Das Konzept ist gut, aber C ++ macht es sehr schwer, lesbar zu schreiben, zumindest für mich. c ++ 0x Lamda-Ausdrücke helfen. Ich mag die Idee von Lamdas sehr. Auf den ersten Blick finde ich die Syntax jedoch sehr hässlich und ich bin mir nicht 100% sicher, ob ich mich jemals daran gewöhnen werde. Vielleicht habe ich mich in 5 Jahren daran gewöhnt und nicht weiter darüber nachgedacht, aber vielleicht auch nicht. Wir werden sehen :)

Ich benutze lieber

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

Ich finde eine explizite for-Schleife klarer zu lesen und die Explizität unter Verwendung benannter Variablen für die Start- und End-Iteratoren reduziert die Unordnung in der for-Schleife.

Natürlich variieren die Fälle, das ist genau das, was ich normalerweise am besten finde.

jcoder
quelle
0

Der Iterator kann ein Aufruf einer Funktion sein, die bei jeder Iteration durch die Schleife ausgeführt wird.

Siehe hier: http://www.cplusplus.com/reference/algorithm/for_each/

sdornan
quelle
2
Nur-Link-Posts geben keine guten Antworten, und wo in diesem Link wird überhaupt etwas angezeigt, das einem aufrufbaren Iterator ähnelt? Ich bin mir ziemlich sicher, dass dieses Konzept einfach keinen Sinn ergibt. Vielleicht haben Sie nur zusammengefasst, was for_eachin diesem Fall die Frage nach den Vorteilen nicht beantwortet.
underscore_d
0

for_eachErlauben Sie uns, das Fork-Join-Muster zu implementieren . Ansonsten unterstützt es Fluent-Interface .

Gabelverbindungsmuster

Wir können eine Implementierung hinzufügen gpu::for_each, um cuda / gpu für heterogen-paralleles Rechnen zu verwenden, indem wir die Lambda-Aufgabe in mehreren Workern aufrufen.

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

Und gpu::for_each kann warten, bis die Arbeiter an allen Lambda-Aufgaben fertig sind, bevor sie die nächsten Anweisungen ausführen.

fließende Schnittstelle

Es ermöglicht uns, von Menschen lesbaren Code präzise zu schreiben.

accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
Shuva
quelle