Verleitet die Mächtigkeit der Sprache C++ dazu es kompliziert zu machen?



  • TyRoXx schrieb:

    In anderen Sprachen, zum Beispiel C#, mache ich mir viel weniger Gedanken über Kleinkram. C# bietet weniger Spielraum für "clevere" Lösungen. Man braucht die auch nicht, weil sowieso alles ineffizient ist.

    Jo, geht mir auch so.

    Heißt aber nicht unbedingt, daß die Programme in Java/C# dann lesbarer werden. Da passieren auf magische Weise manchmal so Dinge wie class ObjectManager, dependency inversion, tiefe Kopien mithilfe eines XML-Serielizers,
    http://www.c-plusplus.net/forum/327984-full



  • volkard schrieb:

    asfdlol schrieb:

    std__boost schrieb:

    std library und boost verleiten zum komplizierten

    Dieser Post ist nicht ausser Acht zu lassen. Statt einer einfachen for -Schleife tendiert man zu std::generate und irgendeinem lokalen Funktor oder einem Lambda. Lesbarer oder "besser" finde ich das nicht.

    "Effective STL" ist ein Buch, wo der Autor einem Seite um Seite einhämmert, daß man ja keine einfachen Schleifen mehr benutzen darf.

    Und womit wird das begründet?



  • Questionmark schrieb:

    volkard schrieb:

    "Effective STL" ist ein Buch, wo der Autor einem Seite um Seite einhämmert, daß man ja keine einfachen Schleifen mehr benutzen darf.

    Und womit wird das begründet?

    Die Funktionen aus <algorithm> sind wegabstrahierter Programmfluss.
    Wenn man eine Schleife selbst schreibt, z.B. so:

    vector<int> result;
    for (int i : v)
    {
        if (i % 2 == 0)
            result.push_back(i);
    }
    

    reicht es nicht, den Schleifenkopf zu lesen um zu verstehen, was passiert. Man muss den ganzen Schleifenkörper, der ja durchaus länger als hier sein kann, anschauen.

    Wenn man allerdings Funktionen aufruft, die diese Schleifen für einen selbst intern umsetzen, erkennt man direkt, was grob passiert.

    vector<int> result;
    copy_if(begin(v), end(v), back_inserter(result), [](int i)
    {
        return i % 2 == 0;
    });
    

    Hier im Beispiel weiß ich direkt einiges, z.B. dass manche Elemente von v nach result kopiert wird, dass in result nachher nichts drin ist, was vorher nicht in v war, dass die Länge von result <= der Länge von v sein wird ...
    Zusätzlich vermeidet man eventuelle Dusseligkeitsbugs wie Endlosschleifen, ungültige Iteratoren usw.

    Der code wird selbstdokumentierender, und man programmiert einfach auf einer höheren Abstraktionsebene.
    Das kann noch weitere Vorteile haben, wie beispielsweise den, dass man irgendwann einfach sein copy_if durch ein hypothetisches copy_if_multithread oder sowas ersetzen kann.

    Klar kann es auch situationen geben, in der dieser funktionale Stil die Lesbarkeit nicht verbessert sondern verschlechtert, oder man aus anderen Gründen besser zur weniger abstrakten Lösung greift. Das muss man dann abwägen und ist auch ein Bischen Frage der persönlichen Vorlieben.

    Die Syntax für sowas ist in C++ vielleicht nicht die schönste, aber mein persönlicher Default ist trotzdem auch hier dieser funktionale Stil.



  • Dobi schrieb:

    Questionmark schrieb:

    volkard schrieb:

    "Effective STL" ist ein Buch, wo der Autor einem Seite um Seite einhämmert, daß man ja keine einfachen Schleifen mehr benutzen darf.

    Und womit wird das begründet?

    Die Funktionen aus <algorithm> sind wegabstrahierter Programmfluss.
    Wenn man eine Schleife selbst schreibt, z.B. so:

    vector<int> result;
    for (int i : v)
    {
        if (i % 2 == 0)
            result.push_back(i);
    }
    

    reicht es nicht, den Schleifenkopf zu lesen um zu verstehen, was passiert. Man muss den ganzen Schleifenkörper, der ja durchaus länger als hier sein kann, anschauen.

    Wenn man allerdings Funktionen aufruft, die diese Schleifen für einen selbst intern umsetzen, erkennt man direkt, was grob passiert.

    vector<int> result;
    copy_if(begin(v), end(v), back_inserter(result), [](int i)
    {
        return i % 2 == 0;
    });
    

    Hier im Beispiel weiß ich direkt einiges, z.B. dass manche Elemente von v nach result kopiert wird, dass in result nachher nichts drin ist, was vorher nicht in v war, dass die Länge von result <= der Länge von v sein wird ...
    Zusätzlich vermeidet man eventuelle Dusseligkeitsbugs wie Endlosschleifen, ungültige Iteratoren usw.

    Der code wird selbstdokumentierender, und man programmiert einfach auf einer höheren Abstraktionsebene.

    Nicht wirklich. Ich wette 100% der Leute die nicht sehr viel mit copy_it und lambdas arbeiten können die for schleife schneller lesen. 99.9% der Leute die sehr viel mit copy_it und lambdas arbeiten können die for schleife schneller lesen. 0.1% der Leute die sehr viel mit copy_if und lambdas arbeiten können die for schleife und das copy_if ding gleich schell lesen. Außderdem hast du es auch noch so formatiert, dass es beim schnellen drüberlesen wie eine Funktion aussieht die i % 2 == 0 zurückgibt.

    Das Argument mit Dusseligkeitsbugs wie Endlosschleifen und ungültige Iteratoren spricht seit for-each-schleifen wohl eher für die for-each-schleife als für irgendwelche iteratoren die man an ein copy_if übergeben muss.

    Dobi schrieb:

    Das kann noch weitere Vorteile haben, wie beispielsweise den, dass man irgendwann einfach sein copy_if durch ein hypothetisches copy_if_multithread oder sowas ersetzen kann.

    parallel for, openmp...



  • nenene... schrieb:

    Außderdem hast du es auch noch so formatiert, dass es beim schnellen drüberlesen wie eine Funktion aussieht die i % 2 == 0 zurückgibt.

    Und bei

    vector<int> result;
    for (int i : v)
    {
        if (i % 2 == 0)
            result.push_back(i);
    }
    

    macht auch nur die Formatierung einen leichten Lesestolpler. Also einmal Klammern und einmal nicht, obwohl beide mal nur ein Befehl behandelt wird. Die Klammern scheinen mir das if irgendwie von der Schleife zu entfernen.

    vector<int> result;
    for (int i : v)
        if (i % 2 == 0)
        {
            result.push_back(i);
        }
    

    nenene... schrieb:

    Das Argument mit Dusseligkeitsbugs wie Endlosschleifen und ungültige Iteratoren spricht seit for-each-schleifen wohl eher für die for-each-schleife als für irgendwelche iteratoren die man an ein copy_if übergeben muss.

    Vorher war das Muster der for-Schleife, die über einen ganzen Container läuft, eigentlich auch so eingebrannt, daß man es fast wie Schlüsselwort gelesen hat. Jo, mit den endlich existierenden for-each-Schleifen wird einiges besser.



  • @nenene...:
    Na gut, dazu wieviel Prozent der Leute momentan das eine oder das andere besser lesen können, weiß ich nicht. Ich gehör zumindest zu der Gruppe, die du gar nicht aufgezählt hast, nämlich zu der, die das copy_if schneller lesen können. 😉
    Ist halt Gewöhnungssache. Und wenn man überall sowas benutzt, ist das return auch nicht mehr überraschend.

    Mit for-each-Schleifen kann man copy_if natürlich nachbauen, aber das kann man auch mit for(auto it ...) oder while oder goto .
    Mir gefallen die zusätzlichen Garantien, die ich durch copy_if bekomme halt sehr gut.

    volkard schrieb:

    Vorher war das Muster der for-Schleife, die über einen ganzen Container läuft, eigentlich auch so eingebrannt, daß man es fast wie Schlüsselwort gelesen hat.

    Vielleicht ist irgendwann ja der algo-funktion-plus-lambda-stil auch so eingebrannt, dass einem ein for-each wiederum komisch vorkommt wenn es nicht gebraucht wird, ebenso wie einem in funktionalen Sprachen ein fold doof vorkommt wenn es ein map oder ein filter auch getan hätte. Ich hoffe es zumindest. 😉



  • volkard schrieb:

    vector<int> result;
    for (int i : v)
        if (i % 2 == 0)
            result.push_back(i);
    

    ftfy.



  • Ethon schrieb:

    Mir fehlt da die Disziplin vollkommen. Deswegen bin ich mit rein objektorientierten Sprachen wie Java deutlich produktiver.

    java rein objektorientiert? Habe ich da was nicht mitbekommen?

    on topic: ich würde eher sagen, je größer der Teil von C++ und Libraries, den ein Entwickler beherrscht, umso größer die Wahrscheinlichkeit, daß übersichtlicher, wartbarer Code entsteht.

    also weniger eine Frage der Mächtigkeit der Sprache, sondern der Kenntnisse des Entwicklers, imho.

    Abgesehen davon:

    Wie ist die Mächtigkeit einer Sprache definiert ?

    Funktionalität pro Codezeile?
    Anzahl äquivalenter Assemblerbefehlszeilen pro (hochsprachlichem) Konstrukt ?
    Oder Skalierbarkeit - von hardwarenah (Zeiger, direkte I/O) bis abstrakt (Template-Metaprog.), von uController bis HPC?



  • großbuchstaben schrieb:

    Abgesehen davon:
    Wie ist die Mächtigkeit einer Sprache definiert ?
    Funktionalität pro Codezeile?
    Anzahl äquivalenter Assemblerbefehlszeilen pro (hochsprachlichem) Konstrukt ?
    Oder Skalierbarkeit - von hardwarenah (Zeiger, direkte I/O) bis abstrakt (Template-Metaprog.), von uController bis HPC?

    Vielleicht daß man weniger oft ein Projekt wegen Sprachbeschränkungen aufgeben muss.

    Keiner weiß es und früher war "Mächtigkeit" ein gutes Schlüsselwort um zu sagen, daß man im weiteren Verlauf des Threads nicht mehr gelesen werden möchte.



  • Seien wir mal ehrlich. Wenn man in der heutigen Zeit bei einem Projekt C++ vermeiden kann, dann tut man dies. Es ist schon schwer eine Sprache zu finden, die so komplex ist und bei der mich sich soo derbe ins Bein schießen kann, wenn man nicht gerade eine Profi ist.



  • tiefesCpp schrieb:

    Seien wir mal ehrlich. Wenn man in der heutigen Zeit bei einem Projekt C++ vermeiden kann, dann tut man dies. Es ist schon schwer eine Sprache zu finden, die so komplex ist und bei der mich sich soo derbe ins Bein schießen kann, wenn man nicht gerade eine Profi ist.

    Wenn es um Geschwindigkeit gehen soll, ist C++ die Sprache der Wahl. GUI-Anwendungen und kleine Apps kann man ja gerne in einer anderen Sprache schreiben, aber wenn man sich mal ueberlegt, was ein Durchschnittsbenutzer alltaeglich verwendet, dann sind das Browser, E-mail-client, ein schlanker und moeglichst schneller Texteditor, PDF-Reader, VLC-Player, Office-Programm, Bildbetrachtung, VLC-Player, Webanwendungen (Facebok, Google, kleine Spiele) und natuerlich die ganzen Betriebssystemskomponenten.

    Ausser E-mail muessen alle Programme an bestimmten Stellen schnell sein und seien es nur die Ladezeiten bei PDF-Reader und Bildbetrachtung. Dafuer bleibt eigentlich nur noch C (wofuer man auch gute Programmierer braucht) und C++.



  • Halte ich für ein Gerücht dass diese Userprogramme irgendwie besondere Ansprüche an Performance stellen würden.
    Notfalls schreibt man halt den kleinen, performancekritischen Teil in einer nativen Sprache (wobei man da lieber C als C++ nimmt) und bindet es im Programm ein.



  • Viele Programme sind fuer meinen Geschmack beim Laden von Daten zu langsam und das liegt nicht nur an der Festplatte, sondern daran dass sie ewig zum Verarbeiten brauchen. Es kann natuerlich auch an schlechten Algorithmen liegen, aber nach meiner Erfahrung gibt es noch genug Sachen mit Nachbesserungsbedarf.



  • Seh ich ähnlich. Aber nur wegen der 2%* Code in denen 97%* der CPU-Zeit draufgehen muss man sich C oder C++ ja nicht auch unbedingt in den anderen 98% Code antun. 😉 Viele "high-level"-Sprachen haben ja ein gutes FFI, und manchmal findet man auch schon eine fertige durchoptimierte lib.

    *Für die Zahlen habe ich keine statistische Grundlage, nur etwas subjektive Erfahrung. Sie sind vermutlich trotzdem meistens falsch, was aber für die eigentliche Aussage nicht so wichtig ist. 🙂



  • Ethon schrieb:

    Halte ich für ein Gerücht dass diese Userprogramme irgendwie besondere Ansprüche an Performance stellen würden.
    Notfalls schreibt man halt den kleinen, performancekritischen Teil in einer nativen Sprache (wobei man da lieber C als C++ nimmt) und bindet es im Programm ein.

    Was machst du eigentlich noch hier? Preist ueberall D an, und wenns mal nicht passt dann C.



  • Ich programmiere C++. 😕
    Finde D besser, ändert aber nichts an der Tatsache dass ich es sehr selten programmiere.



  • Ich habe C++ als erstes gelernt, und das Problem was nun entsteht, wenn ich gezwungen bin eine andere Sprache zu verwenden, ist, dass mir einfach so viel fehlt. Ich verwende nur, was ich brauche und wenn die Usability meines codes einfacher wird durch ein bisschen "Eleganz", dann entscheide ich mich gerne mal dafür.

    Folgendes Beispiel zeigt, warum es mir Wert ist, manchmal an einer Stelle mehr Arbeit zu machen. Nebenbei ist es ein Anti-Beispiel, dass zeigt, wie Komplexität an den richtigen Stellen dazu führt, dass Code super simpel wird.
    (Beispielcode zu meiner eigenen JSON Bibliothek)

    struct SerializableClient : public JSON::FusionStruct<SerializableClient>
    {
        uint32_t UID;
        uint32_t login_fails;
        std::string name;
        std::string ip;
        uint16_t port;
        Package pack; // has been adapted too
    };
    
    BOOST_FUSION_ADAPT_STRUCT
    (
        SerializableClient,
        (uint32_t, UID)
        (uint32_t, login_fails)
        (std::string, name)
        (std::string, ip)
        (uint16_t, port)
        (Package, pack)
    )
    
    //...
    
    {
    // super kurz:
        SerializableClient client;
        js_parse(client, "client", source);
        std::string str = js_stringify("client", client);
    }
    

    (funktioniert mit allen standard containern und mehr)
    (Andererseits könnte man nun sagen, dass andere Sprachen Introspection haben, aber das ist ja nicht mein Punkt :p )



  • Das ist superkurz und elegant und hat noch einen Errorhandling!

    case class User(name: String, email: String, pwd: String)
    
    object User {
      implicit val userReads: Reads[User] = Json.reads[User]
      implicit val userWrites: Writes[User] = Json.writes[User]
      implicit def userToJson(user: User) = Json.toJson(user)
    }
    
    def registerUser = Action(BodyParsers.parse.json) { request =>
      request.body.validate[User] match {
        case jsUser: JsSuccess[User] => // Do something
        case error: JsError => // Error Handling
      }
    }
    


  • IBV schrieb:

    Das ist superkurz und elegant und hat noch einen Errorhandling!

    case class User(name: String, email: String, pwd: String)
    
    object User {
      implicit val userReads: Reads[User] = Json.reads[User]
      implicit val userWrites: Writes[User] = Json.writes[User]
      implicit def userToJson(user: User) = Json.toJson(user)
    }
    
    def registerUser = Action(BodyParsers.parse.json) { request =>
      request.body.validate[User] match {
        case jsUser: JsSuccess[User] => // Do something
        case error: JsError => // Error Handling
      }
    }
    

    Um welche Programmiersprache handelt es sich hier? Ich kann kein Java erkennen.



  • Performance ist oft mehr eine Frage der Umsetzung als der Sprache.
    Ein Bubble-Sort über 35.000 Einträge wird in C++ langsamer sein als in Java.


Anmelden zum Antworten