Smart Pointer statt 'new' und 'delete'?



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



  • Kopierkonstruktor ist anwendungsbedingt, d.h. ich will meine Baeume/Graphen meist nicht kopieren, da es recht kompliziert ist, die Verknuepfung zwischen Knoten zu kopieren. Auch gibt es einfachere Varianten, um Probleme zu vermeiden:

    bin_tree::bin_tree(bin_tree const &other) { 
       for(auto p : other.nodes) {
         std::unique_ptr<node> pnode(new node(p)); // schwierig, dass auf Knotenebene zu machen
                                                   // weil die Nachbarknoten in der Kopie noch nicht existieren koennten
         nodes.push_back(pnode.get());
         pnode.release();
       } 
    }
    

    Nur so am Rande: Wahrscheinlich wuerde ich hier doch eine Liste benutzen.

    Zur Frage:

    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.

    Smart pointer werden bei mir eingesetzt, wenn Besitz transferiert wird, meist an Modul- oder Klassengrenzen. Das ist hier nicht der Fall ( struct node kann wegen mir innerhalb von struct bin_tree definiert werden). D.h. Besitzverhaeltnisse auszudruecken ist nicht noetig. Weiterhin stelle ich mir immer die Frage, wieviel Abstraktion ich gerne haette, was es bringt und was es kostet. Mit unique_ptr fuegt man ein weiteres Level hinzu, kostet wenig (nicht im Sinne von Leistung, aber vielleicht Lesbarkeit), bringt aber auch wenig. Deswegen benutze ich es nicht. Zu guter letzt: konform zu C++03, was leider immer noch wichtig ist.



  • Ich sehe jetzt auch kein Problem mit ptr_vector in dem beschriebenen Fall.
    Oder eben nen Pool-Allocator verwenden.
    Beides garantiert Exception-Sicherheit im Ctor.

    Ich mag nämlich genau so wenig wie seldon "halb" Exception-sicheren Code.

    Natürlich kann man dafür auch den vector in eine Basisklasse auslagern. Das wäre auch OK.
    Wobei man, wenn man diese Variante konsequent weiterdenkt, irgendwann wieder beim ptr_vector landet. Also wieso nicht gleich den verwenden?



  • knivil schrieb:

    Auch gibt es einfachere Varianten, um Probleme zu vermeiden:

    bin_tree::bin_tree(bin_tree const &other) { 
       for(auto p : other.nodes) {
         std::unique_ptr<node> pnode(new node(p)); // schwierig, dass auf Knotenebene zu machen
                                                   // weil die Nachbarknoten in der Kopie noch nicht existieren koennten
         nodes.push_back(pnode.get());
         pnode.release();
       } 
    }
    

    Das reicht noch nicht.
    Was wenn es bei der zweiten Node nen bad_alloc gibt? Wer löscht dann die erste?

    Davon abgesehen musst du dieses unique_ptr/release Spiel überall wiederholen wo du Nodes einfügst - auch nicht schön.

    Eine Lösung für das Freigeben der ersten Node hab' ich ja schon geschrieben (Basisklasse).
    Und für das Einfügen würde ich eher ne Hilfsfunktion verwenden:

    template <class... T>
    bin_tree::node* bin_tree::alloc_node(T&&... args)
    {
        std::unique_ptr<node> p(new node(std::forward<T>(args)...));
        nodes.push_back(p);
        return p.release();
    }
    
    bin_tree::bin_tree(bin_tree const &other)
    { 
        for (auto p : other.nodes)
            alloc_node(p);
    }
    

    Und wenn man das jetzt konsequent weiterdenkt, dann landet man irgendwann bei nem Pool-Allocator.

    Wobei man jetzt diesen Pool-Allocator nicht unbedingt voll aufblasen muss, so dass er alles kann was er können könnte, breitmöglichst einsetzbar ist etc.
    Lieber den Ball flach halten, und nur das implementieren was man braucht.

    Trotzdem finde ich es gut das Single-Responsibility Prinzip einzuhalten.
    Und das hiesse für mich, dass die "sicher newen und wieder deleten" Logik in eine eigene Klasse gehört.

    Und dann sind wir wieder da wo wir (mMn.) hingehören: es gibt nur mehr RAII-verwendende Klassen und "RAII-umsetzende" Klassen, und die "RAII-Umsetzert" tun wenig bis nichts anderes.



  • seldon schrieb:

    ...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.

    Da ist immer noch ein Leck. Vielleicht ist das hier korrekt:

    bin_tree::bin_tree(bin_tree const &other) {
      try {
        for(auto p : other.nodes) {
    	  auto p2 = new node(*p);
    	  try {
            nodes.push_back(p2);
    	  }
    	  catch(...) {
    	    delete p2;
    	    throw;
    	  }
        }
      } catch(...) {
        for(auto p : other.nodes) {
          delete p;
        }
        throw;
      }
    }
    

    Hmm, ist irgendwie voll schwierig Exception-sicher zu programmieren. Wenn C++ dafür bloß eine Lösung hätte...

    hustbaer schrieb:

    Eine Lösung für das Freigeben der ersten Node hab' ich ja schon geschrieben (Basisklasse).

    Wow such OOP. Genau richtig für knivil.



  • Ich kehre zurueck zu meiner Loesung: Kein Kopierkonstruktor. Warum jetzt kuenstlich einen dazubasteln, wenn es von Anfang an nicht vorgesehen war. Wahrscheinlich haette ich dann mit Indizes gearbeitet anstatt mit Zeigern, vielleicht auch nicht. Dann waers ein einfaches copy , vielleicht auch nicht.

    Das Hauptproblem ist, dass kein Destruktor aufgerufen wird, wenn im Konstruktor eine Exception geworfen wird. Das kann aber per Hand geschehen:

    bin_tree::bin_tree(bin_tree const &other) {
       nodes.resize(other.nodes.resize(),0)
       for(unsigned int i = 0; i < nodes.size(); ++i) {
           nodes[i] = new (nothrow) node(other.nodes[i]); // whatever that means in this context
           if (node[i] == 0) {
               this->bin_tree::~bin_tree();
               throw std::bad_alloc ...;
           }
       }
    }
    

    Nun, wuerd ich wahrscheinlich nicht so machen. Ist nicht zu Ende gedacht (e.g. virtual Destruktor). Aber ihr werdet mir hoffentlich gute Argumente aus dem Standard liefern. Und ja, man kann auch einfach ptr_container verwenden.

    Ausserdem: Niemand nimmt sich dem Problem an: benachbarte Knoten sollen nah im Speicher liegen.

    Wow such OOP. Genau richtig für knivil.

    Just another Fan-Boy. Wer argumentiert hat verloren. Du hast gewonnen. Jaja, ich habe RAII nicht verstanden, OOP auch nicht, und ueberhaupt kann ich nicht programmieren, schon gar nicht C++. 🙄



  • knivil schrieb:

    Ich kehre zurueck zu meiner Loesung: Kein Kopierkonstruktor.

    Das ist natürlich OK. Wozu alles copyable wenn mans nicht braucht? Macht keinen Sinn. KISS FTW! 🙂

    Das Hauptproblem ist, dass kein Destruktor aufgerufen wird, wenn im Konstruktor eine Exception geworfen wird. Das kann aber per Hand geschehen:

    bin_tree::bin_tree(bin_tree const &other) {
       nodes.resize(other.nodes.resize(),0)
       for(unsigned int i = 0; i < nodes.size(); ++i) {
           nodes[i] = new (nothrow) node(other.nodes[i]); // whatever that means in this context
           if (node[i] == 0) {
               this->bin_tree::~bin_tree();
               throw std::bad_alloc ...;
           }
       }
    }
    

    Nun, wuerd ich wahrscheinlich nicht so machen.

    Hoffentlich! Die Lösung ist nämlich auch grausam.
    Wenn man sowas 1-2 mal in einem Programm braucht, OK. Nur kenne ich das aus eigenen Projekten. Wenn man erstmal anfängt mit "ist ja nur die eine Stelle" zu argumentieren, dann zieht sich das bald durchs ganze Projekt. *schauder*

    Denn new (nothrow) garantiert ja soweit ich weiss nur dass der 1. operator new Aufruf keine Exception wirft. Der Node-Konstruktor könnte aber eine werfen. D.h. man verlässt sich darauf dass dieser noexcept ist. Finde ich nicht gut, zumindest nicht wenn es einfach zu vermeiden ist.

    Was es in diesem Fall ja - wie ich schon mehrfach geschrieben habe - ist. Und wer jetzt die Nase rümpfen will weil hier Vererbung als Implementierungs-Detail eingesetzt wird, der soll das ruhig machen.

    knivil schrieb:

    Ausserdem: Niemand nimmt sich dem Problem an: benachbarte Knoten sollen nah im Speicher liegen.

    Wenn du mir sagst wie man das mit manuellem delete hinbekommt, dann sag ich dir wie man es ohne macht (bzw. fange an darüber nachzudenken).
    Oder hab' ich da was übersehen?

    Wobei es hier natürlich zwei offensichtliche Lösungen mit RAII gibt:

    • std::vector<Node> + Index
      * Pool-Allokatoren

    Also was ist jetzt die Frage?

    @TyRoXx
    Nein, nix OOP, Implementierungsdetail. Die Hilfsklasse wird natürlich private geerbt.



  • Zunächst Errata: Die Codeschnipsel oben haben beide das Problem, das Tyrox anspricht (und zweiteres auch noch einen Tipfeeler; die zweite Schleife muss nodes, nicht other.nodes behandeln), also Asche auf mein Haupt. Da wünscht man sich make_unique...aber in diesem Fall wäre es wohl ausreichend,

    bin_tree::bin_tree(bin_tree const &other) : nodes(other.nodes.size()) {
      ...
    }
    

    zu schreiben und Zuweisung statt push_back zu nehmen.

    Ansonsten schließe ich mich eigentlich in allem grundsätzlich hustbär an. Der Vererbungstrick ist ziemlich praktisch (und mal eine Gelegenheit, private Vererbung sinnvoll zu benutzen), wenn man mal in die Situation kommt, einen Container wirklich selbst schreiben zu müssen, und im Grunde ist es dann eine Ausprägung von

    hustbär schrieb:

    Und dann sind wir wieder da wo wir (mMn.) hingehören: es gibt nur mehr RAII-verwendende Klassen und "RAII-umsetzende" Klassen, und die "RAII-Umsetzert" tun wenig bis nichts anderes.

    ...da die Basisklasse nämlich nur RAII macht und die eigentliche Klasse den ganzen Interfacekram. Die glibc bspw. macht das beim Vektor etc. übrigens genau so, und das ist aus meiner Sicht auch sinnvoll.

    Wenn die Nodes in einem Container nahe beieinander liegen sollen, ist allerdings mehr gefragt, als sie in einen Vektor zu stopfen, sofern der Payload nicht so klein ist, dass mehr als eine Node in eine Cacheline geht. Da ist die Frage spannend, in welcher Reihenfolge die Nodes später meistens ausgewertet werden, und eine allgemeine Antwort ist schwer zu geben.



  • knivil schrieb:

    bin_tree::bin_tree(bin_tree const &other) {
       nodes.resize(other.nodes.resize(),0)
       for(unsigned int i = 0; i < nodes.size(); ++i) {
           nodes[i] = new (nothrow) node(other.nodes[i]);
           if (node[i] == 0) {
               this->bin_tree::~bin_tree(); //undefiniertes Verhalten
               throw std::bad_alloc ...;
           }
       }
    }
    

    unsigned ist übrigens der falsche Typ für i falls das std::vector sein soll.

    hustbaer schrieb:

    Wenn man sowas 1-2 mal in einem Programm braucht, OK.

    Nein, das ist nicht OK. Das ist das ewige "aber ICH kann damit umgehen". Wieder die Frage: Warum korrekte und bewährte Hilfsmittel über Bord werfen für nichts?

    hustbaer schrieb:

    @TyRoXx
    Nein, nix OOP, Implementierungsdetail. Die Hilfsklasse wird natürlich private geerbt.

    Ich wollte eigentlich schreiben, dass das ziemlich übler Missbrauch von Vererbung ist. Eine Basisklasse nur für einen Destruktor? WTF? Das ist die Art von "clever code", die man nicht haben will.

    seldon schrieb:

    bin_tree::bin_tree(bin_tree const &other) : nodes(other.nodes.size()) {
      ...
    }
    

    zu schreiben und Zuweisung statt push_back zu nehmen.

    Und der nächste Leser freut sich, wenn er herumrätselt, warum das so gelöst worden ist.

    Exceptions sind in C++ so entworfen worden, dass man fast nie etwas Besonderes beachten muss. Man muss Code nicht anders schreiben. Ein Programmierer müsste nicht einmal wissen, dass Exceptions existieren, um die schwache Ausnahmesicherheit einhalten zu können. Das ist in Java zum Beispiel nicht der Fall.

    - Für jede Ressource existiert genau ein Objekt, das sie besitzt. Warum sollten es auch mehrere Objekte sein können? Warum sollte etwas anderes als ein Objekt, also "niemand", eine Ressource besitzen können?

    - Ein Objekt besitzt in der Regel maximal eine Ressource. Mehr als eine Ressource pro Klasse sollte als Fehler betrachtet werden (siehe Single Responsibility Principle).

    Selbst ohne Exceptions ist das eine vernünftige Vorgehensweise, weil nicht viel schief gehen kann. Im Falle einer Exception ist das Programm dann in der Regel automatisch sicher.



  • Some programming tools NOT used by Real Programmers:
    Compilers with array bounds checking. They stifle creativity...

    Soviel dazu, ihr Mädchen.



  • TyRoXx schrieb:

    hustbaer schrieb:

    Wenn man sowas 1-2 mal in einem Programm braucht, OK.

    Nein, das ist nicht OK. Das ist das ewige "aber ICH kann damit umgehen". Wieder die Frage: Warum korrekte und bewährte Hilfsmittel über Bord werfen für nichts?

    Hast du den Teil danach, den du nicht mehr zitiert hast, auch gelesen? Wenn ja, dann: hast du den Teil danach auch verstanden?

    Es ist immer ne Kosten/Nutzen Rechnung.
    Wenn die Kosten ausreichend klein sind es sauber zu machen, dann macht man es auf jeden Fall sauber. Wenn ich aber 1-2 Stellen in einem Programm hab', wo so eine Sauerei angesagt ist, weil die Kosten es sauber zu machen unangemessen hoch wären, dann mach ich mir keinen Kopf.
    Wenn du Herr Evangelist sein willst, und es trotz unvertretbarem Aufwand sauber implementieren willst, dann mach es ruhig.

    TyRoXx schrieb:

    hustbaer schrieb:

    @TyRoXx
    Nein, nix OOP, Implementierungsdetail. Die Hilfsklasse wird natürlich private geerbt.

    Ich wollte eigentlich schreiben, dass das ziemlich übler Missbrauch von Vererbung ist. Eine Basisklasse nur für einen Destruktor? WTF? Das ist die Art von "clever code", die man nicht haben will.

    Na bloss gut dass du's nicht gemacht hast! Das wäre nämlich eine ziemlich dumme Aussage gewesen. 😉

    Klassen die man nur oder hauptsächlich für ihren Destruktor verwendet sind in C++ nicht wirklich so selten. Speziell nicht wenn man die Welt der reinen Anwender verlässt und anfängt sich in der Welt der Library-Entwickler breitzumachen.

    Und es gibt auch keinen guten Grund auf sowas zu verzichten. Es ist einfach, es funktioniert und es ist wasserdicht/deppensicher.

    Wenn du andrer Meinung bist, dann zeig mir eine "bessere" Lösung, die ohne Einsatz von Hilfsmitteln wie ptr_container oder vector<unique_ptr<T>> auskommt.



  • hustbaer schrieb:

    Es ist immer ne Kosten/Nutzen Rechnung.
    Wenn die Kosten ausreichend klein sind es sauber zu machen, dann macht man es auf jeden Fall sauber. Wenn ich aber 1-2 Stellen in einem Programm hab', wo so eine Sauerei angesagt ist, weil die Kosten es sauber zu machen unangemessen hoch wären, dann mach ich mir keinen Kopf.
    Wenn du Herr Evangelist sein willst, und es trotz unvertretbarem Aufwand sauber implementieren willst, dann mach es ruhig.

    Die korrekten Lösungen, ptr_vector und vector<unique_ptr<>> , kosten nichts. Die unsauberen "Lösungen" kosten viel Zeit und Nerven und die wenigsten Programmierer können sie überhaupt fehlerfrei umsetzen.
    Mach doch mal bitte ein Beispiel für sauber vs unsauber. Ich weiß gar nicht was du meinst.



  • Ich meine, wie ich ja schon geschrieben habe, Fälle, wo die "korrekte" (lol BTW) Lösung eben nicht nichts kostet.
    Beispiel dafür müsste ich länger überlegen/rauskramen. Gibt aber so Fälle.

    Es ist mir aber echt Rätselhaft wie du auf Grund von meinen Beiträgen hier auf die Idee kommst, dass ich Gefrickel toleriere oder gar befürworte.



  • Ein vernünftiger Container allein reicht an dieser Stelle leider nicht aus. Bei

    nodes.push_back(new node(*p));
    

    könnte die node erstellt werden und push_back bad_alloc werfen, dann hat man ein Speicherleck. Was man hier eigentlich will, ist make_unique, was man sich bis C++14 halt selbst kurz zusammenkloppen muss. Oder halt in diesem Fall das Problem aus dem Weg räumen, indem man Vektor-Relokationen von vorneherein verhindert. boost::ptr_vector hat das selbe Problem, und durch die Vererbungsnummer (bzw. eine äquivalente Kompositionsnummer, wenn man den zusätzlichen Boilerplate in Kauf nehmen will) wird man es auch nicht sauber los.

    Als Beispiel für einen Fall, in dem der Vererbungstrick (oder eine äquivalente Kompositionsnummer) sinnvoll ist: ich musste vor inzwischen längerer Zeit mal einen Vektor-Ersatz schreiben, der seine Daten solange in einem lokalen aligned_storage hält, bis dieser voll ist und er auf den Heap ausweichen muss. Hintergrund war, dass sich in einem stark nebenläufigen Programm die Threads beim Locken des Heaps ständig gegenseitig im Weg standen und die Anzahl von Allokationen reduziert werden musste. Ich habe Exceptionsicherheit in den Konstruktoren hergestellt, indem ich die Speicherverwaltung in eine private Basisklasse ausgelagert habe. Einen std::vector zu halten, wäre unter den Umständen nicht sinnvoll gewesen.

    Was gerechtfertigte Frickelei angeht, habe ich einen ganz ähnlichen Fall mit gleicher Motivation: Diesmal geht es darum, Laufzeitpolymorphie ohne dynamische Speicherverwaltung zu bekommen. Die Typmenge ist vorher bekannt und statisch (sonst würde das auch nicht funktionieren), also läuft es im Wesentlichen darauf hinaus, ausreichend und passend ausgerichteten Speicher vorrätig zu haben. In C++11 lässt sich das wunderbar verpacken, in C++03 hat man das Perfect-Forwarding-Problem, wenn man ein Objekt in den Speicher instantiieren will. Wenn du eine saubere Lösung für das Problem hast, hab ich massig offene Ohren; ich hab das schlussendlich mit Makros gemacht, was bedeutet, dass einige Teile der Klassenvorlage public sein müssen, die es nicht sein sollten. Eine andere Lösung habe ich stumpf nicht gefunden, und "läuft, aber sieht nicht hübsch aus" ist immerhin besser als "läuft nicht mit dem Compiler, mit dem es laufen muss."



  • seldon schrieb:

    könnte die node erstellt werden und push_back bad_alloc werfen, dann hat man ein Speicherleck.

    Scheinproblem.
    Wie oft kommt sowas schon vor und welche sinnvolle Programmausführung ist dann noch möglich?



  • seldon schrieb:

    Ein vernünftiger Container allein reicht an dieser Stelle leider nicht aus. Bei

    nodes.push_back(new node(*p));
    

    könnte die node erstellt werden und push_back bad_alloc werfen, dann hat man ein Speicherleck. Was man hier eigentlich will, ist make_unique, was man sich bis C++14 halt selbst kurz zusammenkloppen muss. Oder halt in diesem Fall das Problem aus dem Weg räumen, indem man Vektor-Relokationen von vorneherein verhindert. boost::ptr_vector hat das selbe Problem, und durch die Vererbungsnummer (bzw. eine äquivalente Kompositionsnummer, wenn man den zusätzlichen Boilerplate in Kauf nehmen will) wird man es auch nicht sauber los.

    Dafür schreibt man sich eine push_back_or_delete Hilfsfunktion. Ist dann natürlich nicht mehr Deppensicher, weil jemand direkt push_back auf den vector versuchen könnte, aber hey.

    Oder man leitet beinhart vom vector ab, und überdeckt die push_back Funktion mit einer Variante die push_back_or_delete macht.



  • this->bin_tree::~bin_tree(); //undefiniertes Verhalten
    

    Ich habe nicht umsonst um Argumente aus dem Standard gebeten. Immer her damit, Begruendung! Alternativ kannst du auch eine nicht-virtuelle Funktion wie clear() etc. aufrufen wenn du dich am Destruktoraufruf stoerst. Die gibt es hoechst wahrscheinlich sowieso.

    Die korrekten Lösungen, ptr_vector und vector<unique_ptr<>>, kosten nichts

    Chef sagt: Kein Boost! Projekt mit VS 2008 oder schlimmer! Au backe. 🙂

    Hoffentlich! Die Lösung ist nämlich auch grausam.

    Auch so grausam ist das nicht, eher so mittelmaessig im Vergleich zu Code den ich schon sehen musste. Natuerlich darf der Konstruktor keine Exeption werfen, aber der Entwickler hat voll Kontrolle ueber die Hilfsklasse. Sollte also machbar sein.

    die wenigsten Programmierer können sie überhaupt fehlerfrei umsetzen

    Also ich traue es den meisten Entwicklern zu. Denn wenn nicht, dann muesste ich alles selbst machen.

    Klassen die man nur oder hauptsächlich für ihren Destruktor verwendet sind in C++ nicht wirklich so selten.

    Ja. 🙂

    Ich betrachte das sowieso leicht anders. Mir geht es in erster Linie darum, Moeglichkeiten auszuloten. Mit C++03 war es auch moeglich ohne shared_ptr, unique_ptr oder make_unique uebersichlichen, sauberen Code zu schreiben. Zusammenfassend haben wir ptr_container, private Vererbung und new (nothrow) , sofern keine weiteren Einwaende kommen. Ich fuer meinen Teil habe private Vererbung jetzt in mein Repertoire fuer RAII aufgenommen. Aber nicht, weil es mir unbekannt war, sondern ich normalerweise in andere Richtungen gedacht habe. Danke!

    Side node:

    der seine Daten solange in einem lokalen aligned_storage hält, bis dieser voll ist und er auf den Heap ausweichen muss

    tcmalloc/jemalloc je nach Anwendungsfall soll Wunder wirken.



  • qweasdyxc schrieb:

    seldon schrieb:

    könnte die node erstellt werden und push_back bad_alloc werfen, dann hat man ein Speicherleck.

    Scheinproblem.
    Wie oft kommt sowas schon vor und welche sinnvolle Programmausführung ist dann noch möglich?

    Kann ich als Grund nicht gelten lassen.

    Nicht nur weil wie oft es vorkommt und was danach noch möglich ist z.B. von der Grösse des vector s abhängt.
    Wenn der vector beim Reallocate gleich 500 MB auf einmal haben will, und wir es mit nem 32 Bit System zu tun haben...
    ...dann kann das schnell mal fehlschlagen, ohne dass deswegen die Ausführung des folgenden Error-Handling Codes und evtl. des restlichen Programms ein Problem haben müsste.

    Ist mMn. ne recht einfache Sache...
    Jemand der nicht bemerkt was da passieren könnte hat den falschen Job.
    Jemand der solche Dinge bemerkt, aber grundsätzlich drauf scheisst auch.
    Bleiben noch zwei evtl. akzeptable Unterkategorien: Programmierer die sich in jedem Fall immer wieder überlegen ob man es als Scheinproblem klassifizieren kann, und man sich daher die korrekte Implementierung sparen kann, und Programmierer die sich diese sinnlose Überlegung sparen und es gleich korrekt implementieren.

    Ich verschiebe die "zahlt sich das aus" Überlegung bei solchen Sachen auf jeden Fall soweit, bis ich merke, dass es wirklich viel Aufwand wäre. Was es in den seltensten Fällen ist. Spart mir viel sinnloses Kopfzerbrechen.


Anmelden zum Antworten