Smart Pointer statt 'new' und 'delete'?



  • @TNA:
    Ja, die halbe Stunde arbeit wirst du schon hinbekommen. Man kann das ja auch als container adapter implementieren. Ist ja keine Hexerei. Echt nicht.

    @Skym0sh0:
    unique_ptr in einem Container bedeutet, dass man einen pointer container haben will - da der Container der Besitzer ist. Das erspart eine Menge Verwaltungsaufwand es einfach den Container anzuschaffen - denn der weiß ja alles was er wissen muss. Er kann dann auch eine viel bequemere Syntax anbieten.

    shared_ptr dagegen können theoretisch Sinn machen, da diese ja Aussagen "ich weiß nicht wer der Besitzer ist". So könnte es sein, dass man einfach solche Objekte in einen Container packen will und wieder entfernen - die Lebenszeit des Objektes aber nicht direkt mit dem Container gekoppelt ist.

    Das kommt sehr selten vor - ich denke dass shared_ptr generell sehr sehr sehr selten verwendet werden sollte, denn wie gesagt: shared_ptr heisst: Ich weiß nicht bzw. es interessiert mich nicht wer der Besitzer ist. Das sollte die Ausnahme und nicht die Regel sein. Da Besitzrechte klar geklärt gehören.



  • Shade Of Mine schrieb:

    @TNA:
    Ja, die halbe Stunde arbeit wirst du schon hinbekommen.

    Dein Vertrauen ehrt mich, aber ich würde mir nie zutrauen, in einer halben Stunde Code in "STL-Qualität" zu erstellen.

    Shade Of Mine schrieb:

    Man kann das ja auch als container adapter implementieren. Ist ja keine Hexerei. Echt nicht.

    Das hört sich eher nach einer brauchbaren Lösung an.



  • Shade Of Mine schrieb:

    @Skym0sh0:
    unique_ptr in einem Container bedeutet, dass man einen pointer container haben will - da der Container der Besitzer ist. Das erspart eine Menge Verwaltungsaufwand es einfach den Container anzuschaffen - denn der weiß ja alles was er wissen muss.

    Wieso?
    Meistens will ich doch einen PointerContainer, um Polymorphie zu nutzen. Ich stecke viele unterschiedliche Objekte mit gleichem Interface rein. Klar, der Container ist der Besitzer, aber ein unique_ptr im Container würde es doch genauso tun.
    (Überzeug mich!)

    Shade Of Mine schrieb:

    shared_ptr dagegen können theoretisch Sinn machen, da diese ja Aussagen "ich weiß nicht wer der Besitzer ist". So könnte es sein, dass man einfach solche Objekte in einen Container packen will und wieder entfernen - die Lebenszeit des Objektes aber nicht direkt mit dem Container gekoppelt ist.

    Das kommt sehr selten vor - ich denke dass shared_ptr generell sehr sehr sehr selten verwendet werden sollte, denn wie gesagt: shared_ptr heisst: Ich weiß nicht bzw. es interessiert mich nicht wer der Besitzer ist. Das sollte die Ausnahme und nicht die Regel sein. Da Besitzrechte klar geklärt gehören.

    Keine Frage. Dem ist so!



  • Skym0sh0 schrieb:

    Wieso?
    Meistens will ich doch einen PointerContainer, um Polymorphie zu nutzen. Ich stecke viele unterschiedliche Objekte mit gleichem Interface rein. Klar, der Container ist der Besitzer, aber ein unique_ptr im Container würde es doch genauso tun.
    (Überzeug mich!)

    Ich muss Niemanden überzeugen, ich sage dir einfach was besser ist. Du wirst es schon irgendwann einsehen.

    Aber gängige Argumente sind zB Performance und Klarheit des Codes.
    unique_ptr ist langsamer als ein ptr container und das dauernde dereferenzieren im Client Code ist mühsam und unschön.

    Der Grund warum man einen unique_ptr in einen Container packt, ist Faulheit. Wenn wir ptr container in der Standard Library hätten, dann hätten wir diese Diskussion hier nicht.

    Prinzipiell ist das halt eine Sache der Einfachheit: man sieht sich an was automatisch geht und wenn man kein boost hat ist unique_ptr in Container besser als roher Zeiger in Container - so entsteht sowas halt. unique_ptr ist zB eh schon viel besser als früher wo die Leute immer shared_ptr in den Container gepackt haben. Aber es ist halt eine Notlösung mangels vorhandenen ptr container.

    @TNA:
    Dann mach einen Container Adapter - das ist trivial, du musst nur die erase Operationen genauer ansehen, insert kann man 1:1 weiter leiten. Und die Iteratoren sind auch relativ trivial, die Vergleichsoperatoren sind halt etwas schreib arbeit.

    Ich meine das ernst, das ist nicht kompliziert.



  • Shade Of Mine schrieb:

    Aber gängige Argumente sind zB Performance und Klarheit des Codes.
    unique_ptr ist langsamer als ein ptr container und das dauernde dereferenzieren im Client Code ist mühsam und unschön.

    Hast du ein Beispiel wo ein boost::ptr_vector signifikant schneller ist als ein std::vectorstd::unique\_ptr? Der ptr_vector ist ja tief innendrin auch bloß ein std::vector<void*>. Und wenn ich die Implementierung für den boost::ptr_sequence_adapter so überfliege, würde ich eigentlich erwarten, dass der Overhead beider Lösungen in etwa aufs gleiche rauslaufen müsste.

    Der große Vorteil den ich beim unique_ptr sehe ist dass es einfacher ist ein Element aus dem container rauszunehmen und die Ownership mitzunehmen.



  • zB beim realloc des vectors.

    Alleine dass ein unique_ptr einen deleter mitführt macht ihn langsamer. Bei unique_ptr wird jedes Element des Containers für sich einzeln behandelt - bei einem ptr Container wird der ganze Inhalt gleich behandelt.

    Bei unique_ptr ist der Geschwindigkeitsunterschied natürlich nicht die Welt - bei shared_ptr ist das viel schlimmer, aber es ist unnötiger Overhead der ja keinen Mehrvorteil bringt sondern den Code schwerer zu verstehen macht.



  • Artikel zum Thema RAII: Link



  • Shade Of Mine schrieb:

    Alleine dass ein unique_ptr einen deleter mitführt macht ihn langsamer. Bei unique_ptr wird jedes Element des Containers für sich einzeln behandelt - bei einem ptr Container wird der ganze Inhalt gleich behandelt.

    Den Deleter bezahlst du beim Kopieren nicht, da er beim unique_ptr (anders als beim shared_ptr) im Typ codiert ist und keine Laufzeitkosten verursacht.

    Der zweite Punkt ist allerdings interessant. Einen void* kannst du ja einfach mit einem memcpy rüberschaufeln. Beim unique_ptr wirst du wohl nicht drumrumkommen, jedes Element einzeln moven.



  • Meiner Meinung nach ist die Ära von Boost.PointerContainer am Abklingen... wie so vieles in Boost nach C++11. Ich habe keine Pointer-Container mehr benutzt, seit ich unique_ptr habe. Zum einen ist die Schnittstelle in Boost unschön und nicht einheitlich mit der STL, man muss oft unintuitive Dinge basteln und Spezialfälle kennen (z.B. muss man bei boost::ptr_map LValues haben, um einzufügen). Dass es sich intern um Zeiger handelt, wird teilweise ungenügend wegabstrahiert. STL-Algorithmen funktionieren nicht auf Pointer-Containern, stattdessen hat jeder Container seine eigenen Methoden für sort() , merge() , unique() , etc. Wenn man Besitz übertragen will, muss man wieder rohe Zeiger anfassen. Die Implementierung ist äusserst umständlich, was Debuggen ohne mächtige Tools wie die VS-Visualizers verunmöglicht. Ausserdem sind die Kompilierzeiten aufgrund von Abhängigkeiten zu anderen Boost-Bibliotheken vergleichsweise hoch.

    Ich bin nicht grundsätzlich gegen Pointer-Container, aber viele der Vorteile, die sie in C++98 hatten, sind mittlerweile verschwunden. Die schönere Syntax (da automatisch dereferenziert wird) ist das Hauptargument, das heute noch bleibt. Wenn, dann würde ich allerdings eher einen C++11-basierten Adapter schreiben, der auch gut mit der STL und modernen Smart-Pointers interagiert und nicht dauernd Kompromisse auf Kosten der Benutzerfreundlichkeit eingeht.

    Shade Of Mine schrieb:

    Alleine dass ein unique_ptr einen deleter mitführt macht ihn langsamer.

    std::default_delete ist gratis.



  • Shade Of Mine schrieb:

    zB beim realloc des vectors.

    Alleine dass ein unique_ptr einen deleter mitführt macht ihn langsamer.

    Das stimmt einfach nicht, da wird nichts mitgeführt.

    Shade Of Mine schrieb:

    Bei unique_ptr wird jedes Element des Containers für sich einzeln behandelt - bei einem ptr Container wird der ganze Inhalt gleich behandelt.

    Schon mal etwas von Optimierung gehört? Ob der Compiler sich durch PtrContainer oder einen Standardcontainer von unique_ptr wühlen muss, sollte keinen Unterschied machen. Er hat es vielleicht sogar bei der Standardvariante ein wenig leichter, weil die Standardbibliothek und Compiler oft aufeinander abgestimmt sind.

    Boost.PtrContainer waren schon immer ein übler Hack und sind seit den RValue-Referenzen obsolet.
    Die zusätzliche Dereferenzierung bei vector<unique_ptr<T>> bekommt man zum Beispiel mit boost::indirect_iterator weg.



  • Rocker schrieb:

    Artikel zum Thema RAII: Link

    Es ist so aehnlich wie mit den Monad-Tutorials in Haskell. Sobald jemand es halb verstanden hat, fuehlt er sich berufen, ein Tutorial zu schreiben. Wer seine new/deletes nicht ausgleichen kann, sollte ... mehr programmieren ueben oder den Umgang mit Tools wie valgrind lernen.

    Oder anders: Ist wie mit Politik oder Religion. Jeder kann/moechte seinen Senf dazugeben.



  • Knivil, extra für Leute wie dich gibts dort einen Abschnitt, der esoterische Argumente auseinandernimmt 😉

    Lies mal Punkt 3 und 5.



  • knivil schrieb:

    Rocker schrieb:

    Artikel zum Thema RAII: Link

    Es ist so aehnlich wie mit den Monad-Tutorials in Haskell. Sobald jemand es halb verstanden hat, fuehlt er sich berufen, ein Tutorial zu schreiben. Wer seine new/deletes nicht ausgleichen kann, sollte ... mehr programmieren ueben oder den Umgang mit Tools wie valgrind lernen.

    Wie kann man hier 7000 Beiträge haben, aber keinen brillanten Artikel zum wichtigsten Konzept in C++ als solchen erkennen?
    new und delete gleicht man nicht aus. Das macht der Compiler. Unter anderem dazu ist der da. Wenn du das nicht haben willst, schreib in C (schlechte Idee).

    Ja ne ist klar valgrind. Einen Teil der vorhandenen Fehler manuell zu finden ist viel besser als von vornherein kaum welche zu haben?

    Ich weiß, dass viele Programmierer Schwierigkeiten haben, Exceptions (oder Concurrency, Encodings, Komplexitätsklassen, Sprachklassen, ......) zu verstehen. Das ist aber keine Entschuldigung sich ständig gegen offensichtlich korrekte und nützliche Praktiken zu wehren.

    Shade Of Mine schrieb:

    Der Grund warum man einen unique_ptr in einen Container packt, ist Faulheit. Wenn wir ptr container in der Standard Library hätten, dann hätten wir diese Diskussion hier nicht.

    Rate mal warum PtrContainer nicht in std sind und niemals sein werden.



  • Dogma, nein danke!

    C++11 kein new und delete mehr verwendet werden sollte

    Rocker schrieb:

    Lies mal Punkt 3 und 5.

    Ich habe keins dieser Argumente gebracht.

    Wie kann man hier 7000 Beiträge haben, aber keinen brillanten Artikel zum wichtigsten Konzept in C++ als solchen erkennen?

    Weils keiner ist. Wenn das ein Artikel von 2001 waere zu RAII, waers vielleicht toll. Aber mittlerweile ist es nur einer von vielen. Und fuer (fast) jedes Argument kann ich ein Gegenargument oder eine Situation konstruieren, die dem widerspricht.

    Ja ne ist klar valgrind. Einen Teil der vorhandenen Fehler manuell zu finden ist viel besser als von vornherein kaum welche zu haben?

    Ich habe schon lange keine Speicherlecks mehr, mit oder ohne RAII.

    new und delete gleicht man nicht aus. Das macht der Compiler. Unter anderem dazu ist der da. Wenn du das nicht haben willst, schreib in C (schlechte Idee).

    Leg mir keine Worte in den Mund, nie geschrieben, nie gesagt, dass es besser sei C in C++ zu programmieren.

    Das ist aber keine Entschuldigung sich ständig gegen offensichtlich korrekte und nützliche Praktiken zu wehren.

    Zeig mir doch wo ich gegen RAII argumentiere ... ich argumentiere gegen diesen Artikel nicht gegen RAII.

    Ich meine, was soll dieser Code

    int unsafe3()
    {
        A* a = new A;
    
        if (a->f())
        {
            B* b;
            try
            {
                b = new B;
            }
            catch (...)
            {
                delete a;
                throw;
            }
    
            if (b->f())
            {
                int r = b->g();
                delete b;
                delete a;
                return r;
            }
            else
            {
                int r = a->g();
                delete b;
                delete a;
                return r;
            }
        }
    
        delete a;
        return 0;
    }
    

    Mit oder ohne new/delete ist der absolute kacke. Scheinargument. Angenommen, man hat nur C:

    void dennoch_safe(....)
    {
        A* pa = malloc(...);
        if (pa == 0) return;
        dennoch_safe_helfer(..., pa);
        free(pa);
    }
    

    Holla, manuelles RAII in C. Schoen, dass es der Compiler in C++ fuer mich machen kann, aber ich bin ohne RAII noch lange nicht aufgeschmissen.

    Pointer, new, delete sind elementare Bestandteile von C++. Ein Verstaendnis und sicherer Umgang mit diesen ist wichtig. Als Uebung bietet sich an, einmal ganz ohne new/delete Programme zu schreiben, oder wie in C ohne RAII auszukommen. Dann wuerde man auch verstehen, wann was einzusetzen ist, keine plakativen Vortraege a la "Dont use f*cking pointers" halten und RAII nicht als modern bezeichnen. Modern sind die neuen Sprachelemente in C++ als auch neue Elemente der Standardbibliothek, RAII gibt es schon ewig. smart pointer, reference counting pointer, ... gibt's auch schon ewig, sind jetzt aber Teil der Standardbibliothek.



  • knivil schrieb:

    void dennoch_safe(....)
    {
        A* pa = malloc(...);
        if (pa == 0) return;
        dennoch_safe_helfer(..., pa);
        free(pa);
    }
    

    Das kommt jetzt ein bisschen vom Thema ab, aber es ist schön, Ressourcenverwaltung mal besser (richtig?) gemacht zu sehen als die wilden goto-Konstrukte, mit denen man in C-Code für so was inzwischen rechnen muss. 👍



  • knivil schrieb:

    void dennoch_safe(....)
    {
        A* pa = malloc(...);
        if (pa == 0) return;
        dennoch_safe_helfer(..., pa);
        free(pa);
    }
    

    Holla, manuelles RAII in C.

    Es gibt kein manuelles RAII. Das ist die übliche manuelle Ressourcenverwaltung, die man in C oder Assembler macht. Du weißt gar nicht was RAII ist, also was diskutieren wir hier überhaupt?



  • @TyRoXx: Vielleicht hast du auch den Rest gelesen, aber verstanden hast du ihn nicht.

    Du weißt gar nicht was RAII ist, also was diskutieren wir hier überhaupt?

    Lol, nach dem Motto: Du bist dumm, deswegen habe ich recht. You made my day.



  • knivil schrieb:

    Du weißt gar nicht was RAII ist, also was diskutieren wir hier überhaupt?

    Lol, nach dem Motto: Du bist dumm, deswegen habe ich recht. You made my day.

    Ist mir bei deinen Beiträgen hier aber auch in den Sinn gekommen.

    Sag mal was konkretes. Bring mal eines der vielen Beispiele die du angeblich bringen könntest, wo RAII unsinnig/nicht möglich/... ist.
    Dann werden wir ja sehen.

    Bisher hast du nichts gebracht, was greifbarer wäre als der Beitrag von TyRoXx den du hier kritisierst.



  • Hier gehts darum, manuelle Speicherverwaltung wie new / delete direkt gar nicht mehr zu verwenden. Siehe Eingangspost oder http://klmr.me/slides/modern-cpp.pdf :

    dass man in C++11 kein new und delete mehr verwendet werden sollte

    Dann natuerlich die Sache, dass man stattdessen sein new in smart pointer packen muss. Und das selbst bei Containern std::vector mit unique_ptr gegenueber einen pointer container vorzuziehen ist. Was dann letztendlich zu Beispielen und Argumentation fuehrt, warum man ohne smart pointer nicht vernuenftig programmieren kann. Siehe "brillianten" Artikel von www.bromeon.ch/articles/raii.html . Es ist falsch, dass manuelle Speicherverwaltung zwingend zu undurchsichtigen, fehlerhaften Code und Memory Leaks fuehrt. Gegenbeispiel wurde angerissen. Weiterhin gehts nicht um RAII in dem Artikel, sondern um smart pointer. Was ist daran nicht handfest, unkonkret oder schwammig?

    Ich kritisiere. Was erwartest du mehr von Kritik, als dass sie Maengel, hier in der Argumentation, aufzeigt. Wird ein Beweis in Mathe als falsch bewertet, so ist man nicht in der Pflicht einen richtigen zu liefern. Wird Kritik an einem Paper durch den Reviewer geuebt, so ist es nicht seine Pflicht einen besseren zum Thema zu schreiben.

    Aber hier ein Beipsiel fuer manuelle Speicherverwaltung: Container fuer SSE. Hey, ... du benutze ein Container also benutzt du auch RAII ... aber darum ging es nicht. Ich benutze new / delete in einer vector-Klasse ohne smart pointer. Ich schreibe keinen eigenen Allokator, weil SSE-Elemente in Listen, Maps oder Hashtabellen keinen Sinn machen. Ein weiteres Beispiel sind Baeume/Trees wie Quadtrees, wo interne Knotenelemente durch die Klasse Quadtree mittels new / delete genutzt werden. Kein eigener Allokator, weil Knoten nur innerhalb der Klasse sinn machen. Keine smart pointer, weil sie nicht noetig sind. Und wenn man einen eigenen Allokator schreibt, dann benutzt man auch new / delete direkt.

    Mal ueberlegen, wo noch ... ah Bibliotheken, ein echtes Beispiel ohne RAII: Normalerweise benutze ich innerhalb dieser automatisches Speichermanagment, nur wenn Daten hinein/hinaus sollen geht das schlecht. Normalerweise sind alle Funktionen als "extern C" gekennzeichnet und es wird ein C++ Header angeboten, der die Ressourcen mittels RAII kapselt. Jedoch an der reinen API werden besitzende, rohe Zeiger benutzt. Und da wir gerade bei Bibliotheken sind: Interfacing mit Lua als userdata (light/full). Gibt Hilfsklassen, muss man aber nicht benutzen. Es selbst zu erledigen, ist nicht schwer. Man folgt der Anleitung. Natuerlich muss man schon das Konzept verstanden haben. Probleme gibt es bei Mehrfachvererbung.



  • TyRoXx schrieb:

    Shade Of Mine schrieb:

    Der Grund warum man einen unique_ptr in einen Container packt, ist Faulheit. Wenn wir ptr container in der Standard Library hätten, dann hätten wir diese Diskussion hier nicht.

    Rate mal warum PtrContainer nicht in std sind und niemals sein werden.

    Das hätte ich gerne mal beantwortet.

    Und auch, wieso es gut oder schlecht ist eine Standardbibliothek groß (oder klein) zu machen.
    Im Vergleich zu Java ist die Sprachbibliothek von C++ ja extremst mickrig.
    Andererseits gibt es in Java viele Dinge, die nicht gut gelöst oder sogar schlecht sind (z.B. die Date-Klasse). Auch gibt es einige Redundanzen wie String, Stringbuilder und Stringbuffer. (Nur um mal Beispiele zu nennen)

    Also haut mal Argumente raus!


Anmelden zum Antworten