for Schleife vs std::for_each + lambda



  • Ich beschäftige mich seit neustem mit C++11 welches ja doch einige sehr umfangreiche Änderungen mit sich bringt. Vor allem aber stellt sich mir seit neustem die Frage, warum eigentlich std::for_each seine Daseins Berechtigung hat.
    Welchen Vorteil bringt es mir, statt

    std::vector<int> vec;
    for (std::vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)
    {
     // viele viele Dinge
    }
    
    std::vector<int> vec;
    std::for_each(vec.cbegin(), vec.cend(), [](int i)
    {
     // viele viele Dinge
    });
    

    zu schreiben? Hat das performance mäßig einen Vorteil, oder ist das einfach nur Geschmacksache, und für den ein oder anderen schöner, weil man hier den iterator nicht selbst hoch zählen muss?

    mfg



  • Dein Problem ist, dass du for_each falsch benutzt.



  • Toll, und das hat mir jetzt genau wie weiter geholfen?
    Dann klär mich bitte auf.



  • Hat das performance mäßig einen Vorteil, oder ist das einfach nur Geschmacksache, und für den ein oder anderen schöner, weil man hier den iterator nicht selbst hoch zählen muss?

    Theoretisch dürfte die erste Version performance-mäßig einen Vorteil bringen, ja. Ein Lambda wird intern zu einem capture-type + -object verarbeitet. Der Lambda-Ausdruck resultiert in einer capture-object-Temporary. Dieses wird dann per überladenem Funktionsoperator aufgerufen. Ein Funktionsaufruf pro Schleifendurchlauf also.* (Optimierungen des Compilers mal ausgelassen(!)).
    Geschmackssache ist es nicht.
    Und da kommen wir zur meiner Bemerkung. for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt, sondern mit vordefinierten Funktionen oder Lambdas, Funktionszeigern oder sonstigem. Lagere deine Berechnungen in eine Funktion aus:

    void foo(int bar) // Trivialfall
    {
        // viele viele Dinge
    }
    
    std::for_each( std::begin(vec), std::end(vec), foo );
    

    Statt

    for (auto itr = vec.cbegin(); itr != vec.cend(); ++itr)
       foo(*itr);
    

    Toll, und das hat mir jetzt genau wie weiter geholfen?

    Das war ein Wink: Denke darüber nach, wieso for_each wohl in den Standard aufgenommen wurde. Es gab schon vor Lambdas den Grundzweck (wie auch sonst?), und der ist unverändert.



  • Bei for_each weiss man beim Lesen direkt, dass über alle Elemnte iteriert werden soll. Bei for muss man sich erst den Schleifenkopf komplett ansehen.



  • std::vector<int> vec;
    for (auto i : vec)
      // viele viele Dinge
    


  • Sone schrieb:

    Und da kommen wir zur meiner Bemerkung. for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt,

    Doch, daS ist durchaus so vorgesehen. Herb Sutter predigt das auch immer in seinen Vorträgen. Jeder Compiler kann das optimieren.



  • manni66 schrieb:

    Sone schrieb:

    Und da kommen wir zur meiner Bemerkung. for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt,

    Doch, daS ist durchaus so vorgesehen. Herb Sutter predigt das auch immer in seinen Vorträgen.

    Nicht für for_each! Dafür gibt es range-based for!
    Für alle Sachen wie std::sort oder std::copy_if ist das supi, das ist auch vorgesehen! Aber for_each ist ein Algorithmus der eine Funktion auf eine Menge anwendet, da ist ein Lambda fehl am Platz.



  • Sone schrieb:

    manni66 schrieb:

    Sone schrieb:

    Und da kommen wir zur meiner Bemerkung. for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt,

    Doch, daS ist durchaus so vorgesehen. Herb Sutter predigt das auch immer in seinen Vorträgen.

    Nicht für for_each! Dafür gibt es range-based for!
    Für alle Sachen wie std::sort oder std::copy_if ist das supi, das ist auch vorgesehen! Aber for_each ist ein Algorithmus der eine Funktion auf eine Menge anwendet, da ist ein Lambda fehl am Platz.

    Ich würde (zumindest im Moment) auch eher range based for verwenden. Aber wie gesagt, for_each und Lambda ist durchaus so vorgesehen.



  • manni66 schrieb:

    Sone schrieb:

    Und da kommen wir zur meiner Bemerkung. for_each wird nicht in Verbindung mit "in-place" Lambdas benutzt,

    Doch, daS ist durchaus so vorgesehen. Herb Sutter predigt das auch immer in seinen Vorträgen. Jeder Compiler kann das optimieren.

    Herb Sutter predigt das nur wegen MSVC-Limitations.

    Um Visual C++ idiotensicher zu machen, ist der Zugriff auf Iteratoren standardmässig "gecheckt". Das ist langsam, kann aber nicht optimiert werden.

    std::for_each hingegen muss nur einmal checken und kann dann unchecked Iteratoren verwenden. Das ist dann schneller.

    Sone schrieb:

    Nicht für for_each! Dafür gibt es range-based for!

    Schonmal range-based for für einen Subrange verwendet? Der Vorteil an for_each ist, dass es zwei Iteratoren entgegennimmt, das for aber nur einen Range (es sei denn, jemand schreibt Zusatzcode, aber dann ist for_each wieder kürzer).



  • manni66 schrieb:

    Aber wie gesagt, for_each und Lambda ist durchaus so vorgesehen.

    Wo?

    Schonmal range-based for für einen Subrange verwendet? Der Vorteil an for_each ist, dass es zwei Iteratoren entgegennimmt, das for aber nur einen Range (es sei denn, jemand schreibt Zusatzcode, aber dann ist for_each wieder kürzer).

    boost::iterator_range hilft ab! 🤡 Gut, das ist jetzt ein Scherz, für Subranges ist for_each natürlich besser.

    ~P.S.: Alle Welt wird verwirrt weil VC++ wieder schlecht ist? Hach...~



  • baef schrieb:

    Herb Sutter predigt das nur wegen MSVC-Limitations.

    Das ist möglich, ich bezweifle es aber.



  • manni66 schrieb:

    baef schrieb:

    Herb Sutter predigt das nur wegen MSVC-Limitations.

    Das ist möglich, ich bezweifle es aber.

    Dann erkläre mir, wieso. Ob das "vorgesehen" ist interessiert nicht. Entweder ich sehe gute Gründe, for_each standardmäßig so zu nutzen oder ich sehe sie nicht.
    Ich zitiere:

    volkard schrieb:

    Ich glaube auch Gurus kein Wort, wenn sie keine Begründung anbringen können.



  • Sone schrieb:

    Dann erkläre mir, wieso. Ob das "vorgesehen" ist interessiert nicht. Entweder ich sehe gute Gründe, for_each standardmäßig so zu nutzen oder ich sehe sie nicht.

    Siehst du es ein, dass sich for_each sehr leicht in parallel_for_each umschreiben lässt? for_each bietet mehr Abstraktion als ein plain for.



  • Bezüglich irgendwelcher "MSVC-Limitations": Wären mir keine bekannt. MSVC unterstützt sowohl Lambdas als auch std::for_each und range-based for loops. Iteratoren sind in einem Debug Build per default checked, das kann man aber selbstverständlich deaktivieren, was im Release Build natürlich per default passiert...



  • Sone schrieb:

    Dann erkläre mir, wieso. Ob das "vorgesehen" ist interessiert nicht. Entweder ich sehe gute Gründe, for_each standardmäßig so zu nutzen oder ich sehe sie nicht.
    Ich zitiere:

    volkard schrieb:

    Ich glaube auch Gurus kein Wort, wenn sie keine Begründung anbringen können.

    manni66 schrieb:

    Bei for_each weiss man beim Lesen direkt, dass über alle Elemnte iteriert werden soll. Bei for muss man sich erst den Schleifenkopf komplett ansehen.

    Das Argument zieht meiner Meinung nach aber nicht bei range based for, sondern nur bei Schleifen wie in der Frage.



  • #define foreach for
    


  • baef schrieb:

    Um Visual C++ idiotensicher zu machen, ist der Zugriff auf Iteratoren standardmässig "gecheckt". Das ist langsam, kann aber nicht optimiert werden.

    Wer optimiert schon im Debug-Modus?

    Davon abgesehen sind Checked Iterators ein wahnsinnig nützliches Feature. Wenn man nämlich vernünftig C++ programmiert und Abstraktionstechniken wie RAII verwendet, gehören Iteratoren fast zu den häufigsten Fehlerquellen. Das liegt daran, dass sie doch vergleichsweise "low-level" sind. Ich sollte endlich mal eine breite Offensive mit Ranges starten.

    baef schrieb:

    Schonmal range-based for für einen Subrange verwendet? Der Vorteil an for_each ist, dass es zwei Iteratoren entgegennimmt, das for aber nur einen Range

    Das stimmt zwar, allerdings sind die meisten Anwendungsfälle dennoch begin-end-Paare. Oft wären Ranges tatsächlich besser.



  • Ok, also Mal Butter bei die Fische: Range-based-for und std::foreach + lambda nehmen sich performancetechnisch (im Release-Mode [wie Nexus schon sagt, unsinnig das dazuschreiben zu müssen]) nichts, ersteres ist aber besser lesbar und daher zu bevorzugen, gesetzt der Fall man nutzt den richtigen Compiler.

    Können wir das so stehen lassen oder gibt es Einwände?



  • Ich bedanke mich mal für die ganzen Stimmen, die sich hier zu Wort gemeldet haben 😉
    Wenn ich das so lese, lohnt es sich also in keinem Fall durch das Projekt zu gehen, und alle for iterationen durch std::for_each zu ersetzen, dennoch aber zukünftig eher std::for_each verwenden? Das hilft mir dann doch schon weiter.

    mfg


Anmelden zum Antworten