Praxisfrage



  • Entschuldigt den Titel, aber mir ist nichts eingefallen um auf den Punkt zu kommen 🙂

    Und zwar kenne ich 3 (eigentlich eher 2) Arten um zum Beispiel eine Operation für alle Elemente eines Vectors anzugeben. Die Frage ist nun, was in der Praxis am ehesten angewendet wird und am besten auch eine Erklärung warum.

    Möglichkeit 1:

    for (auto i = Test.begin(); i != Test.end(); i++) {
    		std::cout << *i << std::endl;
    	}
    

    Möglichkeit 2(Eigentlich gleich wie 1, allerdings ausgeschrieben und daher vielleicht leserlicher?):

    for (std::vector<int>::iterator i = Test.begin(); i != Test.end(); i++) {
    		std::cout << *i << std::endl;
    	}
    

    Möglichkeit 3(Habe ich aus meinem Buch und habe ich bisher immer verwendet):

    for (int dummy : Test) {
    		std::cout << dummy << std::endl;
    	}
    


  • Naja, da gibt es vieles. Für deine Beispiele, die weder den Container noch die Elemente verändern, gibt es z.B. noch:

    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <vector>
    using namespace std;
    
    void output( int i )
    {
    	cout << i << ", ";
    }
    
    int main()
    {
    	vector<int> Test{ 4, 76, 23, 12, 6, 5 };
    	for_each( begin( Test ), end( Test ), output );
    	copy( begin( Test ), end( Test ), ostream_iterator<int>{ cout, ", " } );
    }
    

    Ich würde aber immer die Variante nehmen, die leicht zu lesen und für möglichst viele Nutzer verständlich ist. ➡ Range-based for loop


  • Mod

    3+1:

    for (auto dummy : Test) {
            std::cout << dummy << std::endl;
        }
    

    Je nach Bedarf auch mit auto-Referenz oder auto-const-Referenz.

    Warum? Am kürzesten, universellsten und bequemsten.


  • Mod

    Wenn schon eine Pauschalvariante, dann wohl eher mit einer Referenz.



  • @Zhavok: Ich vermute mal in der Praxis ist die hier die am meisten genutzte Variante:

    for (size_t i = 0; i < Test.size(); i++) {
        std::cout << Test[i] << std::endl;
    }
    


  • auch mit eckigen Klammern oder mit .at()? Also klar, beides geht, aber auch hier bin ich mir immer unschlüssig^^

    .at gibt halt mehr sicherheit, is aber ja langsamer.



  • Zhavok schrieb:

    auch mit eckigen Klammern oder mit .at()? Also klar, beides geht, aber auch hier bin ich mir immer unschlüssig^^

    .at gibt halt mehr sicherheit, is aber ja langsamer.

    Idealerweise hast du einen Iterator (d.h. schön die C++-Standardbibliothek verwenden) oder eine Referez auf das gewünschte Element (z.B. Range-based for loop). Ansonsten nimmst du []. Die at Funktion nutzt man eigentlich nicht.



  • Schau dir mal No raw loops an.



  • No raw loops find ich aber ziemlich übertrieben. Die Idee ist nett, und vielleicht auch oft gut anwendbar, aber trotzdem hab ich in meinem Code haufenweise loops und werd mir bei einem zwei Zeilen nicht größartig überlegen, wie ich das anders lösen könnte, nur damit keine Schleife im Code vorkommt.



  • Vielen Dank für eure Antworten. Ich denke ich werde da von allem etwas mitnehmen. 👍



  • for-schleife schrieb:

    @Zhavok: Ich vermute mal in der Praxis ist die hier die am meisten genutzte Variante:

    for (size_t i = 0; i < Test.size(); i++) {
        std::cout << Test[i] << std::endl;
    }
    

    mag sein - hat aber auch historische Gründe. Aber nicht der Beitrag mit den meisten Klicks ist auch immer der Beste. Es sollen ja auch Leute Musikpreise erhalten haben, die sie nicht verdienen! Frei nach dem Motte: fresst mehr Schei... Millarden Fliegen können nicht irren.

    Gerade bei der Programmierung im Allgemeinen und bei C++ im Besonderen sollte man sich dafür hüten, Code danach zu beurteilen, wie häufig er vorkommt. IMHO ist der meiste Code, der im I-Net zu finden ist, schlechter Code. Beispiele: 'while(!EOF) Read' und 'getline PLUS parse string' u.v.m.

    Ich empfehle genau wie SeppJ die range-based-for-loop.



  • Mit der Rage-for-loop gehen so viele Dinge so einfach, dass ich mich öfter mal bei solchem Code ertappe:

    double sum = 0;
    for (auto d : v) sum += d;
    

    Dabei sollte man wohl besser std::accumulate nutzen. Oder vielleicht sogar ranges::accumulate , denn begin+end nerven 🙂 Wobei bei accumulate auch nervt, dass man dann die 0 mit richtigem Typ angeben muss, also 0.0 statt 0. Sonst kommt nur int raus.



  • wob schrieb:

    Mit der Rage-for-loop gehen so viele Dinge so einfach, dass ich mich öfter mal bei solchem Code ertappe:

    double sum = 0;
    for (auto d : v) sum += d;
    

    Dabei sollte man wohl besser std::accumulate nutzen. Oder vielleicht sogar ranges::accumulate , denn begin+end nerven 🙂 Wobei bei accumulate auch nervt, dass man dann die 0 mit richtigem Typ angeben muss, also 0.0 statt 0. Sonst kommt nur int raus.

    Es geht halt meistens nicht mal mehr darum, was sich besser und schneller und in weniger Zeichen tippen lässt, sondern was sich im Nachhinein besser liest.
    Als Vergleich ist eben nicht jedes Buch, dass möglichst viele Informationen in wenig Text verpackt, automatisch besser/schöner als ein Buch, dass ein wenig mehr mit dem Satzbau spielt. Vielleicht ein blöder Vergleich, aber mit accumulate und nem lambda weiß eigentlich jeder direkt was passiert (sofern das lambda jetzt nicht übermäßig groß ist).



  • Hallo wob,

    wob schrieb:

    Mit der Rage-for-loop gehen so viele Dinge so einfach, dass ich mich öfter mal bei solchem Code ertappe:

    double sum = 0;
    for (auto d : v) sum += d;
    

    why not?

    wob schrieb:

    Dabei sollte man wohl besser std::accumulate nutzen. Oder vielleicht sogar ranges::accumulate , denn begin+end nerven

    Vorsicht! Grundsätzlich bin ich echter Fan der std::algorithm, aber gerade in diesem speziellen Fall gibt es den argen Fehler:

    vector< double > v = ...;
        double sum = accumulate( begin(v), end(v), 0 );
    

    Diese Codezeile enthält einen dicken Fehler. Das Gemeine daran ist, dass der Code meist fast - oder sogar manchmal genau - das richtige Ergebnis liefert. Und wer schon mal Fehler in nummerischen Algorithmen gesucht hat, der weiß wovon ich rede! Neuerdings gibt's hier ja eine Compiler-Warning, aber das war nicht immer so.

    wob schrieb:

    🙂 Wobei bei accumulate auch nervt, dass man dann die 0 mit richtigem Typ angeben muss, also 0.0 statt 0. Sonst kommt nur int raus.

    ... das hast Du gerade eben hinzugefügt!



  • Genau, BTDT 😞



  • DNKpp schrieb:

    aber mit accumulate und nem lambda weiß eigentlich jeder direkt was passiert (sofern das lambda jetzt nicht übermäßig groß ist).

    Gerade wenn ein Lambda da ist (und vor allem, wenn man noch kein C++14 kann und man somit noch den Typnamen des Lambda-Parameters ausschreiben muss), wird das accumulate schnell unübersichtlich gegenüber der range-for-Loop. Da kann auch clang-format nicht helfen 😉

    Mit dem ranges::accumulate und Projektionen dagegen wird es wieder besser:

    auto sum = ranges::accumulate(v, 0.0, std::plus<>{}, &MyStruct::whatever);
    


  • Zhavok schrieb:

    Die Frage ist nun, was in der Praxis am ehesten angewendet wird und am besten auch eine Erklärung warum.

    Kommt drauf an - ist ja nicht so, dass die Entscheidung eine reine Geschmacksfrage ist. Sobald man zum Bsp beim Iterieren über einen Container den index oder eine bestimmte Reihenfolge braucht ... ist die gute alte for-Schleife doch nicht so schlecht. Wenn vielleicht auch nicht so chic wie range-based for mit auto, mag der trendbewusste Programmierer einwenden.


Log in to reply