Wie lange hat es gedauert bis ihr alle Aspekte von C++ verinnerlicht hattet?



  • Okay, jedoch verstehe ich immer noch nicht, wieso das Kopieren dann (für Handle oder unique_ptr) erlaubt sein soll. Das Handle wird ja anscheinend nicht in mehreren Objekten gleichzeitig gehalten, also ist das Kopieren doch untersagt? Und unique_ptr ist doch auch nicht copyable?



  • Also ich habe shared-Referenzen auf teure Ressourcen, die von vielen Komponenten verwendet werden, die sich aber nicht eigenständig um das ganze geshare kümmern sollen. Mag Faulheit sein, funktioniert aber ganz gut, und sooooo groß ist der overhead jetzt ja nun auch nicht.



  • Eisflamme schrieb:

    Okay, jedoch verstehe ich immer noch nicht, wieso das Kopieren dann (für Handle oder unique_ptr) erlaubt sein soll. Das Handle wird ja anscheinend nicht in mehreren Objekten gleichzeitig gehalten, also ist das Kopieren doch untersagt? Und unique_ptr ist doch auch nicht copyable?

    Natürlich, ein std::unique_ptr bzw. allgemein ein Handle ist in der Regel das Paradebeispiel für einen Typen, der moveable aber eben nicht copyable ist...



  • Okay, dann habe ich das die ganze Zeit einfach nur sprachlich falsch verstanden. Non-copyableness für Handles und unique_ptr machen ja absolut Sinn. 🙂 Wobei ich z.B. Handles habe, die kopierbar sind, aber ihren Schöpfer (einen Pool) kennen, der ihnen bei Bedarf verrät, ob die noch gültig sind. Aber gut.



  • @Eisflamme: Movable semantics bedeuten beim Kopieren immer einen Übergang der Lebenszeitverwaltung zum Zielobjekt. Man kann also von Besitzumstransfer sprechen. Zum Beispiel könnte eine Lib-Funktion ein Objekt erzeugen und dem Aufrufer per unique_ptr returnieren, damit wird schon in der Deklaration vermittel (und später durch die Compilerfehler), dass es sich hier um Besitztumstransfer zum Aufrufer handelt.



  • Decimad schrieb:

    Mag Faulheit sein, [...]

    In der Regel ist es das wohl, ja. shared_ptr sagt wohl meistens: "Ich hab keine Ahnung von den Abhängigkeiten und Besitzverhältnissen in meinem Programm und will mir auch keine Gedanken darüber machen". Ist, zumindest meiner Erfahrung nach, natürlich nicht gerade ein Symptom von gutem Design...



  • Decimad:
    Vielleicht ist das mein Problem die ganze Zeit: Für mich ist das dann einfach kein Kopieren mehr, das ist dann Moven.

    Aber movable semantics heißt doch nicht, dass Kopieren ausgeschlossen ist, meinte dot gerade. Also muss die Besitzübertragung doch nicht automatisch bei Übergabe geschehen, sondern nur, wenn es eben nicht kopierbar ist, also gemoved wird.

    Edit:
    Aber egal, ich hab das alles, glaube ich, schon verstanden. Mir war eben nur nicht so klar, wo ihr das Ausmaß in RValue-Referenzen seht, sodass ihr es als das Feature schlechthin betrachtet. Gut, Smartpointer profitieren ebenfalls enorm davon. Die Erweiterung der Standardbibliothek finde ich jedoch im Generellen auch schon sehr bewegend.



  • Eisflamme schrieb:

    Vielleicht ist das mein Problem die ganze Zeit: Für mich ist das dann einfach kein Kopieren mehr, das ist dann Moven.

    Naja, man könnte Moven jetzt als einen Sonderfall von Kopieren betrachten. Die Grundidee hinter move semantics ist, dass man über rvalue references im Prinzip den copy constructor für die Fälle, wo ein move möglich ist, überladen kann...



  • Hm, könnte man, aber für mich ist das genau so wie bei nem Dateiordner, es gibt Verschieben (Move) und es gibt Kopieren (Copy). Bei Verschieben ist nachher vom Ursprung etwas weg, bei Kopieren eben nicht. Da macht es für mich nicht so viel Sinn als Sonderfall von Kopieren zu betrachten. Und heißt ein Move-Constructor immer noch (überladener) Copy-Constructor?



  • Dass einem immer gleich schlechtes Design vorgeworfen werden muss 😉
    Wann immer ich shared_ptr verwende, dann aus gutem Grund, und mag es nur wesentlich übersichtlicher Quellcode mit viel weniger offenen Implementierungsdetails beim Clientcode sein (Dass und wie Ressourcen geshared werden, soll den gar nicht interessieren). Wenn das schlechtes Design ist, dann mit gutem Gewissen 😉
    Ich bräuchte mal ein Schlechtes-Design-Fähnchen!



  • Eisflamme schrieb:

    Edit:
    Aber egal, ich hab das alles, glaube ich, schon verstanden. Mir war eben nur nicht so klar, wo ihr das Ausmaß in RValue-Referenzen seht, sodass ihr es als das Feature schlechthin betrachtet. Gut, Smartpointer profitieren ebenfalls enorm davon. Die Erweiterung der Standardbibliothek finde ich jedoch im Generellen auch schon sehr bewegend.

    Natürlich, aber rvalue references verändern die Sprache an sich fundamental. Die Erweiterungen der Standardbibliothek sind selbstverständlich auch super und profitieren von rvalue references, lambdas etc., aber wären wohl großteils auch einfach so möglich bzw. waren über boost sowieso bereits vor C++11 verfügbar...

    Edit: Natürlich ist die Einführung eines memory model und die thread-aware abstract machine etc. in C++11 auch ein sehr wichtiger Schritt. Aber diese Dinge ließen sich davor auch über Libraries beherrschen. Oder nehmen wir z.B. variadic templates; die sind natürlich wahnsinnig praktisch und ich möchte nicht darauf verzichten müssen, aber im Prinzip sind das nur Typelists fix in die Sprache gebastelt, also auch wieder etwas, das vor C++11 (zumindest im Wesentlichen) nicht strikt unmöglich war. Die meisten neuen Features sind eben Komfortfeatures und viele davon (variadic templates, auto, lambdas usw.) sind so praktisch, dass ich sie nicht missen wollte. Aber rvalue references erlauben mir, Dinge auszudrücken, die ich zuvor rein prinzipiell, egal wie hoch auch der Aufwand, nicht ausdrücken konnte...

    Z.B. im Umgang mit OpenGL haben sich rvalue references für mich in der Praxis als unbezahlbar herausgestellt.



  • Es ist wahr.



  • Decimad schrieb:

    Dass einem immer gleich schlechtes Design vorgeworfen werden muss 😉

    Das war nicht als Vorwurf gemeint. Es ist einfach nur meiner Erfahrung nach so, dass man vergleichsweise sehr selten tatsächlich Fälle von Shared Ownership hat. In der Regel gibt es wohl meistens einfach einen Besitzer und einige andere Objekte, die dann mit dem Objekt unabhängig von dessen Lebensdauer arbeiten, oft überhaupt nur über ein Interface...

    Kann natürlich sein, dass Reference Counting bei dir tatsächlich in allen Fällen Sinn macht. Ich persönlich halt es eben einfach so, dass ich Shared Ownership als potentielles Symptom dafür sehe, dass ich die Abhängigkeiten in meinem Design noch nicht ganz verstanden habe. Und bisher konnte ich praktisch alle Fälle von Shared Ownership (bei mir meist früher im Umgang mit COM Objekten einfach im Vorbeigehen vorsichtshalber eingebaut) durch Refactoring in etwas Besseres transformieren. Der einzige potentielle Fall von Shared Ownership, an den ich mich aus den letzten Jahren so erinnern kann, wäre der Glyph Cache in meinem Text Renderer. Und auch da versuche ich, noch eine bessere Lösung zu finden..



  • dot schrieb:

    Aber diese Dinge ließen sich davor auch über Libraries beherrschen. [...] Aber rvalue references erlauben mir, Dinge auszudrücken, die ich zuvor rein prinzipiell, egal wie hoch auch der Aufwand, nicht ausdrücken konnte...

    Kennst du Boost.Move? Diese Bibliothek versucht RValue-Referenzen unter C++03 zu emulieren. Boost beweist jedes Mal wieder aufs Neue, wie mächtig C++ ist, das ist schon fast bedenklich... 😮

    Die Idee der Move-Semantik existierte ohnehin schon vor C++11, nur halt nicht direkt in die Sprache integriert. Siehe std::auto_ptr .

    dot schrieb:

    Z.B. im Umgang mit OpenGL haben sich rvalue references für mich in der Praxis als unbezahlbar herausgestellt.

    Klingt interessant, kannst du das erläutern?



  • Nexus schrieb:

    Die Idee der Move-Semantik existierte ohnehin schon vor C++11, nur halt nicht direkt in die Sprache integriert. Siehe std::auto_ptr .

    In der Tat, auto_ptr ist aber eben broken und mit C++11 nicht umsonst deprecated, weil Move-Semantik sich vor rvalue References eben nicht so richtig toll umsetzen ließ...

    Nexus schrieb:

    dot schrieb:

    Z.B. im Umgang mit OpenGL haben sich rvalue references für mich in der Praxis als unbezahlbar herausgestellt.

    Klingt interessant, kannst du das erläutern?

    Naja, da gibt es wohl nicht viel zu erläutern, sämtliche OpenGL Names (Handles) mappen praktisch direkt auf unique_ptr Style RAII. Vor C++11 fand ich es relativ anstregend, RAII auf OpenGL Objekte anzuwenden. Wenn beispielsweise ein Texture Render Target resized werden sollte, gab es keinen einfachen Weg, einer vorhandenen GL::Texture2D einfach eine neue zuzuweisen. Natürlich kann man in OpenGL einfach das Texture Objekt hinter dem Name ersetzen, aber das find ich eher unsauber. Move-Semantik löst dieses Problem. Oder eine Funktion, die ein Bild aus einer Datei lädt, konnte auch nicht so einfach eine GL::Texture2D returnen. Natürlich konnte man einen entsprechenden Texture2D Konstruktor machen, was ich aber wieder als unbedfriedigende Lösung emfpinde, denn die Texture2D sollte sich eben nur um die Verwaltung der Ressource kümmern und nicht auch noch um das Laden der Daten aus allen möglichen Quellen...



  • dot schrieb:

    In der Regel gibt es wohl meistens einfach einen Besitzer und einige andere Objekte, die dann mit dem Objekt unabhängig von dessen Lebensdauer arbeiten, oft überhaupt nur über ein Interface...

    Wie handhabst du Ressourcen wie Bilder/Sounds, die an mehreren Orten benötigt werden?

    Es liegt nahe, wenn ein zentraler Manager die Ressourcen besitzt und Clients diese referenzieren. Aber wie stellst du z.B. sicher, dass nicht eine Ressource entladen wird, wenn sie noch in Gebrauch ist? Oder dass unbenutzte Ressourcen automatisch freigegeben werden? Wenn dazu eine komplexe Logik mit Callbacks und Überwachung der Lebenszeit notwendig ist, könnte man auch direkt shared_ptr nehmen...

    dot schrieb:

    In der Tat, auto_ptr ist aber eben broken und mit C++11 nicht umsonst deprecated...

    Ja, aber das sind technische Mühseligkeiten und Pitfalls. Du sprachst von einer prinzipiellen Unmöglichkeit 😉



  • Sollte man in C++11 überhaupt noch per Referenz übergeben? z.B bei solchen Funktionen:

    std::string get_first(std::vector<std::string> const& vec) {
      return vec.front();
    }
    

    Wenn man per Referenz übergibt, braucht man gar keine Kopie, egal ob der Parameter ein RValue oder ein LValue ist.

    std::string get_first(std::vector<std::string> vec) {
      return vec.front();
    }
    

    Hier müsste der Vektor doch kopiert werden, wenn der Parameter ein LValue ist, oder? z.B wenn man die Funktion so verwendet?

    std::vector<std::string> vec;
    vec.push_back("Hello");
    std::string first = get_first(vec);
    // Do other stuff with vector
    

    Sollte man in so einem Fall also per const Referenz übergeben, oder optimieren das die Compiler?



  • Ja, Übergabe als LValue-Referenz ist nach wie vor sinnvoll. Nur kannst du in C++11 eben RValues besser behandeln.



  • *hust Ähm, Moderator? Wäre es möglich die MoveTheRValue-Diskussion, die zweifellos sehr interessant ist, abzusplitten?



  • Nexus schrieb:

    dot schrieb:

    In der Regel gibt es wohl meistens einfach einen Besitzer und einige andere Objekte, die dann mit dem Objekt unabhängig von dessen Lebensdauer arbeiten, oft überhaupt nur über ein Interface...

    Wie handhabst du Ressourcen wie Bilder/Sounds, die an mehreren Orten benötigt werden?

    Es liegt nahe, wenn ein zentraler Manager die Ressourcen besitzt und Clients diese referenzieren. Aber wie stellst du z.B. sicher, dass nicht eine Ressource entladen wird, wenn sie noch in Gebrauch ist? Oder dass unbenutzte Ressourcen automatisch freigegeben werden? Wenn dazu eine komplexe Logik mit Callbacks und Überwachung der Lebenszeit notwendig ist, könnte man auch direkt shared_ptr nehmen...

    Ich sagte nicht, dass Shared Ownership niemals Sinn macht, ich hab ja auch selbst ein Beispiel gegeben, wann es Sinn machen könnte, das dem Deinen auch gar nicht so unähnlich ist (Glyphen, Bilder, dlls...steckt ja im Prinzip in allen Fällen das gleiche Problem dahinter). Ich stimme dir also natürlich zu, dass es durchaus Fälle von Shared Ownership gibt. Aber nur weil es eben Beispiele gibt, wo es Sinn macht, heißt das noch lange nicht, dass es meistens Sinn macht. Was der Regelfall ist, hängt natürlich teilweise davon ab, in welcher Domäne man sich bewegt. Aber ich denke, dass Shared Ownership doch fast immer eher die Ausnahme als die Regel ist. Und einfach prinzipiell überall shared_ptr zu verwenden ist auf jeden Fall eine ganz schlechte Idee und zeugt nicht von besonders tiefgehendem (bzw. überhaupt irgendeinem) Verständnis der Problematik. Was ich eben eigentlich argumentieren wollte ist, dass, wenn man denn eine Guideline haben will, unique_ptr (Ownership Transfer) eher der Default sein sollte während shared_ptr (Shared Ownership) eher für Spezialfälle reserviert ist...

    Nexus schrieb:

    dot schrieb:

    In der Tat, auto_ptr ist aber eben broken und mit C++11 nicht umsonst deprecated...

    Ja, aber das sind technische Mühseligkeiten und Pitfalls. Du sprachst von einer prinzipiellen Unmöglichkeit 😉

    Ich hab mir ja schon beim Verfassen des entsprechenden Postings gedacht, dass irgendwer was gegen meinen Wortlaut haben wird, aber gut, um das klarzustellen, einigen wird uns auf: Es war prinzipiell unmöglich, Ownership Transfer sauber umzusetzen!?

    Btw: Gab es für Perfect Forwarding auch eine prä C++11 Lösung? 😉


Anmelden zum Antworten