Copy- und Move-Semantik [Split von (std::bind - Warum...)]


  • Mod

    TyRoXx schrieb:

    Mit C++11 hat man versucht den Fehler zu korrigieren ohne inkompatibel zu werden. Das move , wie wir es heute haben, war nie die Wunschlösung.

    Doch. Moven von benannten Objekten sollte explizit sein (und move() ist da IMO eine ziemlich optimale Syntax), kopieren nicht. Moven stellt eine Entleerung des Quelloperanden dar. Das ist unerwartet und spiegelt gerade nicht die Semantik primitiver Typen wieder. Im Übrigen hast du völlig vernachlässigt, dass wir implizite Moves haben, sobald prvalues im Spiel sind.

    Implizit tiefe Kopien wie wir sie haben sind, ob du es glaubst oder nicht, ziemlich sinnvoll.



  • Arcoth schrieb:

    TyRoXx schrieb:

    Mit C++11 hat man versucht den Fehler zu korrigieren ohne inkompatibel zu werden. Das move , wie wir es heute haben, war nie die Wunschlösung.

    Doch. Moven von benannten Objekten sollte explizit sein (und move() ist da IMO eine ziemlich optimale Syntax), kopieren nicht. Moven stellt eine Entleerung des Quelloperanden dar.

    Ja, aber doch nur, weil Move in C++ unvollständig ist. Wenn man das früher bedacht hätte, hätten wir einen Compiler, der das Benutzen einer Variablen nach dem Move gar nicht zulässt. Es könnte automatisch passieren, weil es sicher wäre.

    Arcoth schrieb:

    Im Übrigen hast du völlig vernachlässigt, dass wir implizite Moves haben, sobald prvalues im Spiel sind.

    Und weiter?

    Arcoth schrieb:

    Implizit tiefe Kopien wie wir sie haben sind, ob du es glaubst oder nicht, ziemlich sinnvoll.

    Noch einmal die Frage: Warum?


  • Mod

    TyRoXx schrieb:

    Arcoth schrieb:

    TyRoXx schrieb:

    Mit C++11 hat man versucht den Fehler zu korrigieren ohne inkompatibel zu werden. Das move , wie wir es heute haben, war nie die Wunschlösung.

    Doch. Moven von benannten Objekten sollte explizit sein (und move() ist da IMO eine ziemlich optimale Syntax), kopieren nicht. Moven stellt eine Entleerung des Quelloperanden dar.

    Ja, aber doch nur, weil Move in C++ unvollständig ist. Wenn man das früher bedacht hätte, hätten wir einen Compiler, der das Benutzen einer Variablen nach dem Move gar nicht zulässt. Es könnte automatisch passieren, weil es sicher wäre.

    Ich verstehe. Der Nutzen deines Vorschlags ist also, dass wir niemals eine Gelegenheit verpassen würden, zu moven. Der Nachteil besteht darin, überall .clone() ranzuhängen.

    Arcoth schrieb:

    Im Übrigen hast du völlig vernachlässigt, dass wir implizite Moves haben, sobald prvalues im Spiel sind.

    Und weiter?

    Wir verschwenden nicht so viele Moves, wie du annimmst. Das geschieht eigentlich recht selten. Daher bin ich auch skeptisch.



  • Arcoth schrieb:

    Der Nachteil besteht darin, überall .clone() ranzuhängen. (...) Wir verschwenden nicht so viele Moves, wie du annimmst. Das geschieht eigentlich recht selten. Daher bin ich auch skeptisch.

    Meiner Erfahrung nach ist das Kopieren von teuren Objekten viel seltener als das Moven. Wenn ich heute eine Anwendung in C++14 schreibe, ist das Kopieren extrem selten. Ich musste etwas suchen, aber ich habe tatsächlich eine Stelle gefunden, wo ich aus Sicherheitsgründen und Bequemlichkeit auf move verzichte. Es geht um einen shared_ptr , der von einem Lambda-Ausdruck per Kopie gebunden wird. Ich fürchte, dass hier bei zukünftigen Änderungen versehentlich ein use-after-move/Nullzeigerzugriff entstehen könnte. In dem Kontext tut es nicht weh den shared_ptr nur zu kopieren, aber pay-for-what-you-use ist es auch nicht ganz. Bei so etwas würde implizites Move das Verlangen nach Premature Optimization gefahrlos befriedigen. Ich habe in dieser Anwendung ansonsten keine Stelle gefunden, wo ich etwas Teures absichtlich kopiere. Ich würde es wahrscheinlich nicht bemerken, wenn Standard-Container auf einmal nicht mehr kopierbar wären. Deswegen würde mir ein .copy() überhaupt nicht weh tun.

    P.S: Danke für den Split. Ich wollte ursprünglich gar nicht so ausschweifen und ich das eigentliche Thema vergessen.



  • TyRoXx schrieb:

    hustbaer schrieb:

    TyRoXx schrieb:

    Ich halte implizite, teure Kopien generell für einen Design-Fehler von C++.

    Und ich für einen der grössten Vorteile.

    Warum?

    Weil Value-Semantik, ohne dass man grossartig was dafür tun muss, unglaublich praktisch ist.
    Und das "ohne dass man grossartig was dafür tun muss" impliziert dass der Compiler die nötigen Funktionen automatisch generiert. Und wenn man Templates ohne Hilfsfunktionen und spezielle "du musst in Templates alles anders schreiben" Regeln haben möchte, dann muss auch ein

    einVector = einAndererVector;
    

    einfach so gehen. Und muss kopieren, nicht moven.

    Vielleicht liessen sich Regeln finden das, und das was dir vorschwebt, unter einen Hut zu bringen. Nur damit wären wir wieder bei: '98 waren wir einfach noch nicht soweit. Also ist C++ so wie es ist, und ich bin's froh. Denn sonst hätte ich vermutlich die letzten 15 Jahre C oder Java programmieren müssen. Beides eine Vorstellung die mich nicht mit Freude erfüllt.

    TyRoXx schrieb:

    hustbaer schrieb:

    Ändert nämlich nix daran dass die Zeit '98 einfach nicht reif für so eine Sprache war. Sich heute hinstellen und sagen "man hätte ja nur müssen" ist billig. Und sinnlos.

    Ist es verboten über so etwas nachzudenken und zu veröffentlichen?

    Nein, ist nicht verboten.
    Ich hab' bei deinen Beiträgen nur immer den Eindruck, und das recht bestimmt, dass es dir hauptsächlich darum geht Dinge schlechtzureden. Und dass du grundsätzlich davon überzeugt bist dass dein Standpunkt der einzig richtige ist. Siehe Beitrag "Microservices".

    TyRoXx schrieb:

    Man hat jetzt dreißig Jahre gebraucht, um den Leuten einzuhämmern, dass const& überall hinzuschreiben total super ist, obwohl es noch nie eine Lösung für alle Fälle war. Ich verstehe jetzt so langsam den heftigen Widerstand gegen RAII.

    Welcher heftige Widerstand gegen RAII? Wo? Beim ollen Linus? Sonst noch wo?

    TyRoXx schrieb:

    RAII war vor C++11 einfach eine Premature Pessimization.

    Ne. Vermutlich stimmt deine Definition des Begriffs einfach nicht mit meiner überein.
    Niemand hat dich in C++98/03 gezwungen std::string/std::vector by value zu übergeben oder zu returnen.

    TyRoXx schrieb:

    Was waren denn die anderen Alternativen? Höllisch aufpassen und swap / auto_ptr / ptr_vector benutzen oder C-Style nackte Handles benutzen. Ich verstehe, dass viele nach dem Prinzip "keep it simple" bei C stehen geblieben sind, weil C++ eben nicht eine Lösung für alles hatte.

    Weil C++ nicht alles automagisch gut macht bei C bleiben. Ja, kann man machen. Wenn man Masochist oder doof ist.

    TyRoXx schrieb:

    Mit C++11 hat man versucht den Fehler zu korrigieren ohne inkompatibel zu werden. Das move , wie wir es heute haben, war nie die Wunschlösung. Es ist nur ein Kompromiss.

    Korrekt.

    TyRoXx schrieb:

    Die Idee, dass sich alles wie ein C-Typ automagisch kopieren lässt, war doch nicht so der Bringer.

    Ähm, doch, eigentlich schon.
    Es ist nur nicht ausreichend.
    C++11 move ist ein Schritt in die richtige Richtung. Vielleicht geht's in C++2x weiter, ähnlich dem was du oben skizziert hast. Ich sehe da erstmal noch nix was nicht möglich sein sollte.
    Bis auf den Teil dass string t = s; ein Move ist, und ich explizit clone() schreiben muss wenn ich klonen will. Was ich aber, wie ich oben hoffentlich schon klargemacht habe, auch gar nicht unbedingt haben wollen würde.



  • ich denke, man kann das historisch verstehen. Einer der Vorteile von C gegenüber Assembler waren lokale Variablen, und sogar automatisch durch Kopie der Aufruf-Argumente initialisiert. Der Programmierer muß sich nicht darum kümmern, den Stackpointer zu verschieben oder Register mit den Argumenten belegen. In Assembler mußte man das selbst programmieren, oder eben die Argumente by-reference per Zeiger übergeben.

    Ich stimme Tyrox zu: move und ein clone-Befehl (statt copy und ein move-Befehl) wären aus heutiger Sicht in vielen Fällen sinnvoller. Gut, daß es optimierende Compiler gibt.


  • Mod

    TyRoXx schrieb:

    RAII war vor C++11 einfach eine Premature Pessimization.

    Ne. Vermutlich stimmt deine Definition des Begriffs einfach nicht mit meiner überein.
    Niemand hat dich in C++98/03 gezwungen std::string/std::vector by value zu übergeben oder zu returnen.

    NB: Copy elision hatten wir damals auch.

    Und implizite Moves haben wir auch seit C++11:

    std::string f() {
      std::string s;
      // […]
      return s; // Ohne copy elision gibt es hier ein move.
    }
    

    Das könnte sich auf "last odr-use" erweitern lassen. Wäre aber recht umständlich.



  • @Arcoth
    Ja, OK, hast Recht.
    Aber 1. konnte man sich speziell bei C++ Compilern der ersten Generation nicht auf Copy-Elision verlassen, und dann gibt es Fälle wo es halt doch nicht reicht. Sonst wäre move ja auch sinnlos 🙂



  • hustbaer schrieb:

    Weil Value-Semantik, ohne dass man grossartig was dafür tun muss, unglaublich praktisch ist.
    Und das "ohne dass man grossartig was dafür tun muss" impliziert dass der Compiler die nötigen Funktionen automatisch generiert.

    Was hat das jetzt mit dem zu tun, was ich geschrieben habe? Was muss man denn bei meinem Vorschlag "großartig tun"? Ich habe jetzt nicht string clone() const = default; geschrieben, aber das kann man natürlich auch machen.

    hustbaer schrieb:

    Und wenn man Templates ohne Hilfsfunktionen und spezielle "du musst in Templates alles anders schreiben" Regeln haben möchte, dann muss auch ein

    einVector = einAndererVector;
    

    einfach so gehen. Und muss kopieren, nicht moven.

    Was wäre so schlimm daran zu schreiben:

    using std::clone;
    einVector = clone(einAndererVector);
    

    hustbaer schrieb:

    Ich hab' bei deinen Beiträgen nur immer den Eindruck, und das recht bestimmt, dass es dir hauptsächlich darum geht Dinge schlechtzureden. Und dass du grundsätzlich davon überzeugt bist dass dein Standpunkt der einzig richtige ist. Siehe Beitrag "Microservices".

    Ich bin offen für Vorschläge, wie ich mich freundlicher ausdrücken kann.

    hustbaer schrieb:

    Welcher heftige Widerstand gegen RAII? Wo? Beim ollen Linus? Sonst noch wo?

    So ziemlich überall. Mach mal irgendein C++-Projekt außerhalb der Boost-Blase auf. Oder frag einen durchschnittlichen professionellen Programmierer nach Destruktoren: "Ja, ich schreibe immer brav überall Destruktoren" zeugt nicht von Sachverstand.

    hustbaer schrieb:

    TyRoXx schrieb:

    RAII war vor C++11 einfach eine Premature Pessimization.

    Ne. Vermutlich stimmt deine Definition des Begriffs einfach nicht mit meiner überein.
    Niemand hat dich in C++98/03 gezwungen std::string/std::vector by value zu übergeben oder zu returnen.

    Man hat alles per const& übergeben und dann unnötig kopiert. Das meine ich mit Pessimization.

    klassenmethode schrieb:

    Ich stimme Tyrox zu: move und ein clone-Befehl (statt copy und ein move-Befehl) wären aus heutiger Sicht in vielen Fällen sinnvoller. Gut, daß es optimierende Compiler gibt.

    Die helfen aber überhaupt nicht, wenn man explizit implizit eine Kopie anlegt. Compiler können Copy noch nicht zu Move optimieren.


  • Mod

    hustbaer schrieb:

    @Arcoth
    Ja, OK, hast Recht.
    Aber 1. konnte man sich speziell bei C++ Compilern der ersten Generation nicht auf Copy-Elision verlassen, und dann gibt es Fälle wo es halt doch nicht reicht. Sonst wäre move ja auch sinnlos 🙂

    Ack. Da es gerade passt: Copy elision wird bald forciert sein, siehe P0135.

    Ich hab' bei deinen Beiträgen nur immer den Eindruck, und das recht bestimmt, dass es dir hauptsächlich darum geht Dinge schlechtzureden.

    Daran ist nichts falsch, wenn Dinge im Grunde alle schlecht sind.



  • Arcoth schrieb:

    Ich hab' bei deinen Beiträgen nur immer den Eindruck, und das recht bestimmt, dass es dir hauptsächlich darum geht Dinge schlechtzureden.

    Daran ist nichts falsch, wenn Dinge im Grunde alle schlecht sind.

    Bloss die wenigsten Dinge sind ausschliesslich schlecht. Die meisten Dinge haben gute und schlechte Seiten. Die guten zu ignorieren damit man auf den schlechten rumreiten kann macht für mich irgendwie keinen Sinn.



  • TyRoXx schrieb:

    Was wäre so schlimm daran zu schreiben:

    using std::clone;
    einVector = clone(einAndererVector);
    

    Es würde zumindest beim Schreiben von Templates nerven. Wenn man z.B. ein Template schreiben will das mit Bignum Typen genau so wie mit int/float funktioniert, dann müsste man ebenso überall clone() schreiben. Stelle ich mir einigermassen lästig vor.

    Und... weil ich mir diesbezüglich nicht ganz sicher bin: Meinst du jede Klasse sollte bei "=" immer nen move machen, oder nur welche die speziell als "ich will bei = nen move machen" markiert sind? Letzteres wäre vermutlich gerade noch OK.

    Noch eine Frage dazu: Was machst du bei Typen die überladene Operatoren haben? Denk an Bignum Klassen und Strings. Was soll da passieren wenn ich

    a = b + c * d;
    

    schreibe?
    Wird hier dann unnötig ein neues Objekt erstellt, oder ist danach z.B. c im "moved from" Status?

    TyRoXx schrieb:

    hustbaer schrieb:

    Welcher heftige Widerstand gegen RAII? Wo? Beim ollen Linus? Sonst noch wo?

    So ziemlich überall. Mach mal irgendein C++-Projekt außerhalb der Boost-Blase auf. Oder frag einen durchschnittlichen professionellen Programmierer nach Destruktoren: "Ja, ich schreibe immer brav überall Destruktoren" zeugt nicht von Sachverstand.

    Das beantwortet meine Frage nicht. Viele der "professionellen" Programmierer die ich kenne setzen RAII nicht ein weil sie es nicht verstanden haben. Das hat mit Widerstand nichts zu tun, das ist einfach blanke Unkenntnis/Unfähigkeit. Die würden es auch schaffen mit jeder denkbaren anderen Sprache schlechten Code zu schreiben. Je weniger man falsch machen kann, desto weniger denken sie. Und desto mehr vollkommen bekloppte Wege finden sie Dinge langsam zu machen.

    Ein Beispiel aus der Praxis, gerade vor 2 Tagen gesehen: Wir haben zwei (meist fast identische) Listen (Array-basiert, genaugenommen System.Collections.Generic.List ), Grössenordnung 1000 Elemente. Wir müssen einen antijoin machen, also rausbekommen was in Liste 2 drinnen ist, was in Liste 1 nicht vorkommt. Wie macht man das? Na ganz einfach: mit zwei verschachtelten for-Schleifen. *facepalm*

    TyRoXx schrieb:

    hustbaer schrieb:

    TyRoXx schrieb:

    RAII war vor C++11 einfach eine Premature Pessimization.

    Ne. Vermutlich stimmt deine Definition des Begriffs einfach nicht mit meiner überein.
    Niemand hat dich in C++98/03 gezwungen std::string/std::vector by value zu übergeben oder zu returnen.

    Man hat alles per const& übergeben und dann unnötig kopiert. Das meine ich mit Pessimization.

    Meinst du jetzt so Sachen wie z.B. bei push_back ?
    Wenn ja, dann würde ich das eher nen Tradeoff nennen. Der meiste Code den man schreibt ist halt nicht performancekritisch, und was mir hilft dabei weniger Fehler zu machen ist für mich erstmal ne gute Sache. Wenn es Programmierer dann auch an Stellen verwenden wo Performance wichtig ist ... ist das wieder ein anderes Problem.


Anmelden zum Antworten