[Video, Englisch, Herb Sutter]: Netter Vortrag zum Thema "modernes C++"



  • XSpille schrieb:

    vec.push_back(std::move(std::unique_ptr<Foo>(new Foo(0))));
    

    Vermeide überflüssige std::move-Aufrufe und bau Dir zwecks Ausnahmesicherheit ein make_unique. 😉 (siehe Deinen anderen Thread)

    template<class T, class...Args>
    inline unique_ptr<T> make_unique(Args&&...args) {
      return unique_ptr<T>(new T(std::forward<Args>(args)...);
    }
    

    Ich wüsste jetzt nicht, warum make_shared langsamer sein sollte als dieses make_unique.

    Der Vorteil von unique_ptr ist aber, dass er ein release() anbietet. 🙂



  • XSpille schrieb:

    vec.push_back(std::move(std::unique_ptr<Foo>(new Foo(0))));
    

    Hier kannst du das move sowieso weglassen, wir habens hier ja schon mit einem RValue zu tun 😉

    Was shared_ptr angeht: Sehe ich so wie XSpille. Ich habe shared_ptr erst 1-2 mal gebraucht. In allen anderen Fällen hat mir unique_ptr gereicht.



  • krümelkacker schrieb:

    void func(unique_ptr<foo>, unique_ptr<bar>) {}
    
    int main() {
      func(unique_ptr<foo>(new foo), unique_ptr<bar>(new bar));
    }
    

    ist nicht Ausnahmesicher.

    Danke für den Hinweis! Generell wußte ich es, hätte es aber nicht beachtet ^^
    Aber in der Regel, speichere ich solche Objekte vor dem Funktionsaufruf in Variablen, so dass es exceptionsicher ist. Naja... Oder im Forum mal in kurzer Form wie im Post oben 🙄 Aber ist ja nur ein Parameter 🤡

    krümelkacker schrieb:

    Was lässt Dich glauben, dass make_shared langsamer als new ist?

    Ich glaube nicht, dass es bei der Erzeugung (merkbar - eine zusätzliche 0 Zuweisung(?) und Mutex-Initialisierung(?)) langsamer ist, aber wenn man sich das Objekt aus dem Vektor holt und es zuweist, dann wird der Mutex zu erhöhen des Zählers gelöscht (oder sehe ich etwas falsch). Wenn man nur mit der Referenz auf den shared_ptr arbeitet, macht er ja nicht wirklich Sinn, oder? Und gerade in C++0x/C++11-Zeiten mit auto hat man so einen unnötigen lock schnell drin.

    Gruß,
    XSpille



  • 314159265358979 schrieb:

    XSpille schrieb:

    vec.push_back(std::move(std::unique_ptr<Foo>(new Foo(0))));
    

    Hier kannst du das move sowieso weglassen, wir habens hier ja schon mit einem RValue zu tun 😉

    Da hast du allerdings Recht...
    Da siehst du, dass ich wie ich im Post gerade gesagt habe, normalerweise erst in einer Variable zwischenspeicher... 🙄

    EDIT: Ich glaub das std::move ist bezüglich Optimierung sogar schädlich 🕶



  • XSpille schrieb:

    Ich glaube nicht, dass es bei der Erzeugung (merkbar - eine zusätzliche 0 Zuweisung(?) und Mutex-Initialisierung(?)) langsamer ist, aber wenn man sich das Objekt aus dem Vektor holt und es zuweist, dann wird der Mutex zu erhöhen des Zählers gelöscht (oder sehe ich etwas falsch). Wenn man nur mit der Referenz auf den shared_ptr arbeitet, macht er ja nicht wirklich Sinn, oder? Und gerade in C++0x/C++11-Zeiten mit auto hat man so einen unnötigen lock schnell drin.

    Da gibt's keinen Mutex. Der Referenzzähler wird eher als "atomic int" implementiert werden. So oder so hinkt der Vergleich auch, da Du unique_ptr nicht kopieren kannst, nur moven. Und bei shared_ptr's move-ctor erwarte ich, dass er genauso effizient sein wird, da hier der Referenzzähler gar nicht angefasst werden muss.



  • Was ich meine ist, dass euer Fokus im Moment total auf Exception-Safety in eurem Implementierungsdetail (Welchen außenstehenden interessiert schon, dass ihr Zeiger in einem vector speichert?) liegt. Zumindest liest es sich für mich im Moment so. (Zu denen Herb sagt: macht was ihr wollt um die volle Geschwindigkeit rauszukitzeln, wenn da unique_ptr und sowas nahe-optimal und automatisch Exception-Safe ist, nur zu!)

    Ein anderer Fall wären allerdings geteilte Ressourcen oder solche dynamische Dinge, die tatsächlich die Schnittstellengrenzen überschreiten, bei denen die Lebenszeitverwaltung auch ohne Exceptions eine enorme Problematik darstellen kann. Hier halte ich den Ratschlag mit shared_ptr nicht für so abgrundtief falsch und mit make_shared natürlich bestmöglich umgesetzt.

    Damit hat er doch eigentlich alles erschlagen, finde ich. Das Problem war maximal, dass er die Ausnahme (20 Minuten?) vor der Faustregel nannte.



  • In der MSVC Implementierung wirds über AtomicIncrement und AtomicDecrement gemacht. Gibt dazu auch ein nettes Video, das ich gerade nicht finde.





  • Wurde im Beispiel mit dem set<Widget> load_huge_data() nicht sowieso schon immer die Kopie wegoptimiert?



  • krümelkacker schrieb:

    Der Referenzzähler wird eher als "atomic int" implementiert werden.

    Hatte ich nicht in Betracht gezogen... Aber somit hast du aber einen Zugriff der Caching verhindert(, oder?) und minimale Speicherverschwendung.

    Herb Sutter schrieb:

    macht was ihr wollt um die volle Geschwindigkeit rauszukitzeln, wenn da unique_ptr und sowas nahe-optimal und automatisch Exception-Safe ist, nur zu!

    👍

    Ich bin (inzwischen 🤡 ) absoluter Befürworter von unique_ptr.

    krümelkacker schrieb:

    So oder so hinkt der Vergleich auch

    Klar kommt es auf die Anforderungen an. Aber in sehr vielen Fällen, benötigt man den Referenzzähler, der wie ich denke deswegen unnötig ist. Beim unique_ptr hat man auch einen leichten Overhead, der sich vermutlich auch manchmal wegoptimieren läßt(?), aber mit dem Bonus der Exceptionsicherheit.

    Der shared_ptr hat zweifelsohne auch einen wichtigen Nutzen, jedoch (auch wenn) ich mich wiederhole, in meinen Augen sollte er nicht als 'default' verwendet werden.

    EDIT: Für Objekte, die sich von verschiedenen Resourcen geteilt werden, ist für mich ein shared_ptr auch ein absolutes muss 👍



  • Ich finde den Link zum Video nicht, wäre jemand so freundlich, ihn mir zu geben?



  • Dravere schrieb:

    Oder gleich hier:
    http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T

    Gerade streikte scheinbar der Server...



  • "that's not the pointer you're looking for" 😃



  • XSpille schrieb:

    krümelkacker schrieb:

    Der Referenzzähler wird eher als "atomic int" implementiert werden.

    Hatte ich nicht in Betracht gezogen... Aber somit hast du aber einen Zugriff der Caching verhindert(, oder?) und minimale Speicherverschwendung.

    Wo? Wann? Im Vergleich zu was? Wie gesagt, wenn Du einen shared_ptr move-konstruierst, muss man den Referenzzähler nicht anfassen. Und wenn du per make_shared das Ding erzeugst, bleibt es bei einer einzigen Allozierung. Gut, die fällt dann etwas größer aus, da "strong counter", "weak counter", und ein oder zwei Zeiger dabei ist/sind.

    XSpille schrieb:

    Beim unique_ptr hat man auch einen leichten Overhead, der sich vermutlich auch manchmal wegoptimieren läßt(?), aber mit dem Bonus der Exceptionsicherheit.

    Was für ein Overhead? Wenn Du eine Implementierung verwendest, wo sizeof(unique_ptr<int>) != sizeof(int*) gilt, geh Dich beim Hersteller beschweren mit dem Stichwort "empty base class optimization". Sonst wüsste ich gerade nicht, was Du für einen Overhead meinen könntest.

    hustbaer schrieb:

    "that's not the pointer you're looking for" 😃

    Ja, da musste ich auch schmunzeln. 😃



  • krümelkacker schrieb:

    Wo? Wann? Im Vergleich zu was? Wie gesagt, wenn Du einen shared_ptr move-konstruierst, muss man den Referenzzähler nicht anfassen.

    Wann konstriert man einen shared_ptr denn mal per move? Ich gehe im Moment einfach von einem Vektor aus, über den ich iteriere. Entweder ich iteriere mit Hilfe einer Referenz auf die Objekte, dann brauch ich keinen shared_ptr, sondern kann auch einen unique_ptr nehmen oder einen Container, der den Besitz behält.
    Ich glaube das ist so ein klassischer Fall. Sobald man natürlich jetzt mit mehreren Threads auf den Vektor zugreift und keine klare Zuordnung mehr hat, wem das Objekt gehört, dann ist natürlich ein shared_ptr auch meiner Meinung nach sehr zu empfehlen. Vielleicht denkst du gerade auch an einen komplett anderen Anwendungsfall und wir reden aneinander vorbei... Lass es mich wissen 🙂

    krümelkacker schrieb:

    Was für ein Overhead?

    Sobald du eine virtuelle Funktion mit rvalue-Referenz als Parameter hast.
    Als Programmierer, weißt du evtl. per Konvention, dass man das Objekt nach dem Funktionsaufruf nicht mehr löschen darf, aber der Compiler muss von beiden Fällen ausgehen und muss innerhalb der Funktion den Zeiger auf 0 setzen und der Destruktor von unique_ptr muss einen delete 0 ausführen



  • XSpille schrieb:

    krümelkacker schrieb:

    Wo? Wann? Im Vergleich zu was? Wie gesagt, wenn Du einen shared_ptr move-konstruierst, muss man den Referenzzähler nicht anfassen.

    Wann konstriert man einen shared_ptr denn mal per move?

    Dann, wenn man ihn mit unique_ptr vergleichen will, zum Bleistift. Du behauptest doch, beim Einsatz von shared_ptr wo auch unique_ptr gereicht hätte, gäb's einen Laufzeit-Overhead. Wenn dem so ist, musst Du auch so fair sein, shared_ptr-Instanzen auch move-zu-konstruieren, da unique_ptr-Instanzen ja auch nie kopiert werden.

    XSpille schrieb:

    krümelkacker schrieb:

    Was für ein Overhead?

    Sobald du eine virtuelle Funktion mit rvalue-Referenz als Parameter hast.
    Als Programmierer, weißt du evtl. per Konvention, dass man das Objekt nach dem Funktionsaufruf nicht mehr löschen darf, aber der Compiler muss von beiden Fällen ausgehen und muss innerhalb der Funktion den Zeiger auf 0 setzen und der Destruktor von unique_ptr muss einen delete 0 ausführen

    Du schnippelst mir da zuviel vom Kontext weg. Ich weiß schon gar nicht mehr, worum's ging. *nochmal-nachguck* Aha, ein angeblicher Overhead von unique_ptr. Aber im Vergleich wozu? Zu einem Raw-Pointer? Naja, wenn Du überall einen raw-Pointer statt unique_ptr einsetzt und die fehlenden Operationen (delete) manuell ausführst, kannst Du dir vielleicht das ein oder andere Setzen/Überprüfen auf 0 sparen. Dass das irgendeinen nennenswerten Unterschied in der Laufzeit macht, wage ich zu bezweifeln.



  • Auch Java ist nicht ernsthaft langsamer als C++. Wir sollten alle nach Java umsteigen.



  • Ich habe nur gesagt, dass es in vielen Fällen wie mit Kanonen auf Spatzen schiessen ist, wenn man einen shared_ptr verwendet. Abstrakt gefragt: Wozu in der Sahara statt eines normalen Stahlbolzen einen Bolzen verwenden, der bis -30°C haltbar ist?
    Ich denke es ist offensichtlich, dass er einen höheren Speicherverbrauch hat und dafür brauche ich ein Argument, das mir diesen rechtfertigt. Die 3 Zeilen Code sind es für mich nicht. Selbst wenn du das Objekt an mehreren Stellen verankerst ohne genaue Besitzdefinition ist für mich shared_ptr noch lange nicht zwangsläufig die richtige Wahl. Solange alles in einem Thread geschieht, reicht für mich ein 'normaler' nicht-threadsicherer Zähler aus. Wenn natürlich verschiedene Threads darauf arbeiten, würde ich ebenfalls einen shared_ptr verwenden.

    Wenn ich jetzt ein Pfennigfuchser wäre, dann würde ich sagen, dass bei einem move zusätzlich zum Kopieren des Zeigers auf das Objekt auch noch des Zeiger auf den Counter kopiert werden muss... Aber darum geht es mir eigentlich nicht.
    Mir geht es darum, dass alle immer den Porsche nutzen, obwohl ein Clio ausreicht.

    Bezüglich des unique_ptr:
    Ich bin (generell) ein Befürworter des unique_ptr und wollte nur klarstellen, dass er auch einen minimalen Overhead haben kann. Übrigens wollte ich ursprünglich 'meistens' statt 'manchmal wegoptimiert' schreiben, dachte mir aber sei lieber vorsichtig 😉 Ich sehe es absolut wie du, dass dieser Unterschied nicht nennenswert ist. Diesen nehme ich (i. d. R.) wegen Exceptionsicherheit gerne in Kauf. Hier habe ich einen klaren Nutzen.

    Wenn ich den Nutzen des threadsicheren Referenzzählers verwenden möchte, dann kann ich gerne den shared_ptr verwenden. Klar kommt das vor, aber nicht so oft, dass ich dessen Verwendung als 'default' anpredigen würde.



  • (Herb Sutter, Microsoft) && modernes "C++" in einem Wort.
    Hahahahahahahahahahaha

    Die Deppen von M$ haben ja noch nicht mal die einfachsten Sachen von C++11 umgesetzt. Mit modernem C++ hat das garnichts zu tun. Das Ding überhaupt C++ zu nennen grenzt schon an frechheit.

    Nur zum vergleich:

    http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx

    vs

    http://gcc.gnu.org/gcc-4.7/cxx0x_status.html <----- So muss das aussehen

    Zum Glück kann ich richtig modern Programmieren unter einem richtig modernen OS (Linux)

    ________________________________

    "Es ist besser für den Pinguin zu Sterben, als zu Microsoft zu leben"
    - Mein Motto



  • Die Vortragende auf der Build-Konferenz wollen sich und ihr Produkt natürlich auch verkaufen. Das nervte schon ein bisschen.

    Gleich nachdem Sutter sagte, dass der Microsoft Compiler der erste gewesen sei, der Rvalue-Referenzen unterstützt hat, hat er sich korrigiert: einer der ersten Compiler.

    Bei seiner Verwendung von std::for_each in den Beispielen habe ich gedacht: "Das benutzt Du nur, weil ihr noch keine for-range-Schleife im Compiler implementiert habt". Aber einen Vorteil hat std::for_each dann doch noch. Es sagt genau das aus, was da passiert: Eine Schleife über alle Elemente, während im for-range Loop ein break versteckt sein könnte.

    Auch ich habe mich über den "prefer shared_ptr and make_shred as default over new" gewundert und an unique_ptr gedacht. Aber er relativiert das ja später...

    Ob der Microsoft-Compiler nun schon viel C++11 kann oder nicht, sein Vortrag ist immer noch gut.


Anmelden zum Antworten