Smart Pointer statt 'new' und 'delete'?



  • 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!



  • knivil, bring doch mal ein Argument, warum man RAII nicht verwenden sollte, obwohl es nicht wegzudiskutierende Vorteile bringt. Und zwar konkreter als nur "es ist nicht nötig" bzw. "es geht auch ohne".

    Ich rede jetzt nicht von new/delete zur Implementierung irgendwelcher Datenstrukturen, und der Artikel auch nicht. Es geht um new/delete in normalem Anwendercode. Was gewinnt man, wenn man automatisierbare Arbeit von Hand erledigt?



  • Skym0sh0 schrieb:

    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.

    Man hat PtrContainer nur erfunden, weil RValue-Referenzen fehlten. Die gibt es inzwischen, also sind die Dinger obsolet.

    Skym0sh0 schrieb:

    Und auch, wieso es gut oder schlecht ist eine Standardbibliothek groß (oder klein) zu machen.

    Die verschiedenen Hersteller tun sich schon schwer genug damit die überschaubare Bibliothek von C++ korrekt zu implementieren. Lieber Qualität als Quantität. Was fehlt denn unbedingt?
    Was passiert, wenn voreilig standardisiert wird, sieht man an iostream s.

    Skym0sh0 schrieb:

    Im Vergleich zu Java ist die Sprachbibliothek von C++ ja extremst mickrig.

    Kann man in Java inzwischen in weniger als 10 Zeilen eine UTF-8-Datei in einen String einlesen?



  • TyRoXx schrieb:

    Man hat PtrContainer nur erfunden, weil RValue-Referenzen fehlten. Die gibt es inzwischen, also sind die Dinger obsolet.

    Nur schade, dass vector<unique_ptr<T> > in der Benutzung eine ganz andere Syntax als ptr_vector<T> hat und man für generischen Code lieber bei der Syntax von vector<T> bleibt, also fehlen weiterhin Sprachmittel und der ptr_vector ist notwendig.



  • aherdfxcvyadfgadf schrieb:

    Nur schade, dass vector<unique_ptr<T> > in der Benutzung eine ganz andere Syntax als ptr_vector<T> hat und man für generischen Code lieber bei der Syntax von vector<T> bleibt, also fehlen weiterhin Sprachmittel und der ptr_vector ist notwendig.

    So generisch ist der Code ohnehin nicht, wenn PtrContainer im Spiel sind. Wie im früheren Post angetönt, ist die Schnittstelle an diversen Stellen unterschiedlich, und nicht selten sind diese Unterschiede auf C++98-Probleme zurückzuführen, die mittlerweile nicht mehr existieren (Exceptionsicherheit ohne Move-Semantik, die ganzen auto_ptr -Geschichten, ...). Daher wären langfristig C++11-Adapter sinnvoller als die veralteten PtrContainer.

    Schön wäre sowieso, wenn sich für C++11 eine Bibliothek wie Boost bilden würde, aber mit weniger Rückwärtsgewandtheit, weniger TMP-Overengineering und etwas mehr Pragmatismus. Momentan läufts dauernd auf Selbst-Basteln heraus, sobald man irgendwas Modernes braucht...



  • @knivil
    Das einzige (für mich) interessante Beispiel das du bringst sind die Quadtrees.

    Denn beim SSE Container schreibst du selbst eine RAII Klasse (den SSE Container)... ich hoffe ich muss jetzt nicht weiter darauf eingehen warum das Beispiel für mich nicht gilt.

    Und die Sache mit den Bibliotheken... wenn man mit Programmteilen interfacen muss die nicht mehr C++ sind, dann ist auch irgendwie klar dass man RAII nur begrenzt verwenden kann.
    OK, es gilt natürlich trotzdem als Beispiel wo RAII nicht möglich ist, insofern ist das OK.

    Aber um auf den Quadtree zurückzukommen: was soll "keine smart pointer, weil sie nicht noetig sind" heissen?
    Nötig sind sie nie, man kann immer selbst delete in den Destruktor schreiben. Nur finde ich es schlecht es zu tun, weil es schlechter lesbar und schlechter wartbar ist.
    Also warum ist da der Quadtree eine Ausnahme?



  • Nun, ich glaube wir reden von unterschiedlichen Sachen. Ich kritisiere das Dogma, keine rohen Zeiger, direktes new / delete zu verwenden. RAII ist fuer mich genauso ein Muss. Aber um deine Frage zu beantworten:

    Der Einfachheit halber mit Binaerbaeumen klassisch:

    struct node {
        node* left;
        node* right;
    }
    struct bin_tree {
        node* root;
        // some operations for constructing, destructing and inserting
    }
    

    Dem Dogma folgend koennen alle rohen Zeiger durch unique_ptr ersetzt werden und man spart sich die eigenen Aufrufe von delete :

    struct node {
        unique_ptr<node> left;
        unique_ptr<node> right;
    }
    struct bin_tree {
        unique_ptr<node> root;
    }
    

    Soweit so gut, jetzt erhoehen wir die Anforderungen wie sie beispielsweise bei einem Baum/Graph mit raeumlicher Knotenzuordnung (spatial partition) gewuenscht ist:
    1.) benachbarte Knoten sollen nah im Speicher liegen (Ausnutzen des Prozessorcache)
    2.) es ist kein Baum mehr sondern ein planarer Graph

    Fuer 1.) bedeutet es, wie kann ich einen Teilbaum innerhalb eines vorgefertigten Speicherblocks konstruieren und organisieren. Fuer 2.) bedeuted es, wie ich die Knoten organisiere, damit ich z.B. zyklische Referenzen beseitige. In beiden Faellen wird mir unique_ptr, shared_ptr oder weak_ptr keine grosse Hilfe sein.

    Fuer 2.) ist eine einfache Loesung, Speicher vo Knoten auf Bau-/Graphebene zu verwalten anstatt auf Knotenebene:

    struct bin_tree {
        std::vector<node*> nodes;
    
        ~bin_tree() {
             for(auto e: nodes) delete nodes;
        }
    }
    

    Auf Ebene der Knoten ist es manuelles Speichermanagment, auf Ebene des Benutzers, der nur bin_tree zu Gesicht bekommt, ist es RAII.

    Mit Problem 1.) musst ich noch nicht loesen, doch so schoen Graphen auch sind, es ist hart, das letzte bischen Leistung herauszuholen.



  • knivil schrieb:

    Fuer 2.) ist eine einfache Loesung, Speicher vo Knoten auf Bau-/Graphebene zu verwalten anstatt auf Knotenebene:

    struct bin_tree {
        std::vector<node*> nodes;
    
        ~bin_tree() {
             for(auto e: nodes) delete nodes;
        }
    }
    

    Auf Ebene der Knoten ist es manuelles Speichermanagment, auf Ebene des Benutzers, der nur bin_tree zu Gesicht bekommt, ist es RAII.

    Warum nicht

    struct bin_tree {
        std::vector<std::unique_ptr<node>> nodes;
    }
    

    ? Oder meinethalben auch einen boost::ptr_vector, das ist dann Nebensache. In den Baumknoten kannst du ja immer noch rohe Zeiger benutzen.



  • seldon schrieb:

    Warum nicht

    struct bin_tree {
        std::vector<std::unique_ptr<node>> nodes;
    }
    

    ? Oder meinethalben auch einen boost::ptr_vector, das ist dann Nebensache. In den Baumknoten kannst du ja immer noch rohe Zeiger benutzen.

    Das ist einfach nur Panne.
    Hoffe, die C++-Gemeinde findet irgendwann wieder den Weg zurück.



  • knivil schrieb:

    Dem Dogma folgend koennen alle rohen Zeiger durch unique_ptr ersetzt werden und man spart sich die eigenen Aufrufe von delete

    Von welchem Dogma sprichst du genau? Zumindest der RAII-Artikel sagt ja explizit, dass solche Fälle von new/delete legitim sind. Aber das kapselst du gegen aussen auch schön weg. Problematisch ist, wenn sich der Anwender ständig mit manueller Speicherverwaltung rumschlagen muss. nicht der Implementierer der Datenstruktur ein einziges Mal.

    knivil schrieb:

    Auf Ebene der Knoten ist es manuelles Speichermanagment, auf Ebene des Benutzers, der nur bin_tree zu Gesicht bekommt, ist es RAII.

    Genau so ist es ja gut... Sind wir uns nun einig oder wie? 🙂



  • volkard schrieb:

    Hoffe, die C++-Gemeinde findet irgendwann wieder den Weg zurück.

    Wohin? C? Assembler?



  • manni66 schrieb:

    volkard schrieb:

    Hoffe, die C++-Gemeinde findet irgendwann wieder den Weg zurück.

    Wohin? C? Assembler?

    C++



  • volkard schrieb:

    manni66 schrieb:

    volkard schrieb:

    Hoffe, die C++-Gemeinde findet irgendwann wieder den Weg zurück.

    Wohin? C? Assembler?

    C++

    Und das ist, wenn man delete schreibt?



  • manni66 schrieb:

    volkard schrieb:

    manni66 schrieb:

    volkard schrieb:

    Hoffe, die C++-Gemeinde findet irgendwann wieder den Weg zurück.

    Wohin? C? Assembler?

    C++

    Und das ist, wenn man delete schreibt?

    Unfug. Warum antworte ich eigentlich noch auf unregs?

    Zwischenfrager schrieb:

    knivil schrieb:

    Dem Dogma folgend koennen alle rohen Zeiger durch unique_ptr ersetzt werden und man spart sich die eigenen Aufrufe von delete

    Von welchem Dogma sprichst du genau? Zumindest der RAII-Artikel sagt ja explizit, dass solche Fälle von new/delete legitim sind. Aber das kapselst du gegen aussen auch schön weg. Problematisch ist, wenn sich der Anwender ständig mit manueller Speicherverwaltung rumschlagen muss. nicht der Implementierer der Datenstruktur ein einziges Mal.

    knivil schrieb:

    Auf Ebene der Knoten ist es manuelles Speichermanagment, auf Ebene des Benutzers, der nur bin_tree zu Gesicht bekommt, ist es RAII.

    Genau so ist es ja gut... Sind wir uns nun einig oder wie? 🙂



  • volkard schrieb:

    seldon schrieb:

    Warum nicht

    struct bin_tree {
        std::vector<std::unique_ptr<node>> nodes;
    }
    

    ? Oder meinethalben auch einen boost::ptr_vector, das ist dann Nebensache. In den Baumknoten kannst du ja immer noch rohe Zeiger benutzen.

    Das ist einfach nur Panne.
    Hoffe, die C++-Gemeinde findet irgendwann wieder den Weg zurück.

    Das sehe ich anders. Ich kann, da du keine Begründung angegeben hast, über deine Beweggründe nur spekulieren; im Vergleich zum Code mit nackten Zeigern ergibt sich (vom kürzeren Code abgesehen) vor allem ein Unterschied für die Schwierigkeit, in Konstruktoren von bin_tree Exceptionsicherheit zu garantieren.

    Stell dir vor, einen Kopierkonstruktor für bin_tree zu schreiben:

    bin_tree::bin_tree(bin_tree const &other) {
      for(auto &p : other.nodes) {
        nodes.push_back(new node(*p)); // oder p->clone() oder wasauchimmer.
      }
    }
    

    ...geht mit unique_ptr wunderbar. Mit nackten Zeigern ist der Code fehlerhaft, und das ist ein einfach zu machender Flüchtigkeitsfehler.

    bin_tree::bin_tree(bin_tree const &other) {
      try {
        for(auto p : other.nodes) {
          nodes.push_back(new node(*p)); // oder p->clone() oder wasauchimmer.
        }
      } catch(...) {
        for(auto p : other.nodes) {
          delete p;
        }
        throw;
      }
    }
    

    ist für mein Auge weniger hübsch.


Anmelden zum Antworten