"Container" für beliebig viele Variablen eines beliebigen Types implementieren



  • Sooo,

    jetzt ist jede menge const und & drin 😃
    Und eine hoffentlich ausreichende Doku 🙂

    Ihr findet den dokumentierten Code unter

    http://pastebin.com/v2SMS23E (treeContainer.h)
    http://pastebin.com/TMvCTrZ1 (treeContainer.cpp)

    Hinweise auf Verbesserungen (am Code oder der Doku) werden wie immer gerne angenommen 😉

    Viele Grüße
    Cherup



  • remove, removeToEnd und removeToStart schauend redundant aus, ein remove(from, to) sollte reichen.

    Aber Classes wird die Doku etwas komisch, finde ich. Node, SentinelNode. Warum braucht man eine SentinelNode Klasse und warum steht die in der Doku???
    Member: std::shared_ptr myRoot. Kann an der Stelle noch nichts damit anfangen, finde ich nur den Namen komisch. Zur Info: das My in MySql ist der Name der Tochter des Entwicklers. Ich gehe davon aus, dass du keine Tochter mit diesem Namen hast, also ist die Bezeichnung myRoot nicht so cool.

    Iteratoren: It iterates through all node until - böser Rechtschreibfehler!
    Ich weiß nicht, obs mir gefällt, dass es verschiedene Klassen für Iteratoren gibt. Vielleicht ist es besser so, ich bin nicht tief genug drin in der Thematik. Spontan hätte ich aber eher erwartet, dass es nur eine öffentliche Iterator Klasse gibt und dafür verschiedene Funktonen, um einen zu bekommen:
    iterator childIterator();
    iterator nodeIterator();
    Intern können die dann unterschiedliche Implementierungen haben. Es wäre einfacher, solche Iteratoren als Parameter zu übergeben und man müsste sich nicht um so viele Details kümmern. Aber wie gesagt, kann ich spontan nicht wirklich beurteilen.

    std::shared_ptr<placeholder> myValue = nullptr;
    Initialisierungen mit nullptr schauen komisch aus. Das ist eine RAII Klasse, die hat einen vernünftigen Default Constructor. Und wegen my hab ich schon was geschrieben.
    Viele Getter Funktionen sind noch nicht const. Und shared_ptr am besten auch als const& übergeben. Atomare Operationen sind nicht soo billig, wenn man die sich sparen kann, gibt es keinen Grund, das nicht zu tun.

    std::vector<std::shared_ptr<Node>> myChildren;
    Da muss ich mich erst recht fragen, warum du irgendwelche Sentinel brauchst.

    remove sollten normalerweise einen Iterator auf das nächste Element zurückgeben und nicht auf das vorherige.

    std::shared_ptr<placeholder> myValue = nullptr;
    Auf den ersten Blick... Warum hält der Node den Wert als shared_ptr? Er müsste doch eigentlich die Ownership haben?

    Bei internen Klassen würde ich nicht so viele Getter und Setter machen, das ist eher Boilerplate.

    Naja, über den eigentlichen Code hab ich kaum drübergeschaut, das ist viel schwieriger zu lesen, als eine Doku.



  • Danke für die vielen Hinweise und mit vielem hast du Recht.
    Das my ist eine alte Angewohnheit aus der Zeit als ich programmieren gelernt habe. Habs mir noch nicht abgewöhnt.

    Mechanics schrieb:

    Node, SentinelNode. Warum braucht man eine SentinelNode Klasse und warum steht die in der Doku???

    Warum die SentinelNodes? Um eindeutige Start- und Endpunkte für die Iteratoren, insbesondere die child_iteratoren zu schaffen. Wenn man mit einer Schleife durchiteriert braucht man ein Abbruchstatement. Das wird durch die end() funktion gegeben. Dazu muss die end()-Funktion aber auf einen Node hinter dem letzten bzw. vor dem ersten Node zeigen, sonst wird eben nur bis zum vorletzten node iteriert.
    Warum in der Doku? Einfach der Vollständigkeit halber, ist vielleicht eine ungünstige Stelle dafür.

    Mechanics schrieb:

    iterates through all node until - böser Rechtschreibfehler!

    *hust* dazu sage ich mal besser nichts...*schäm*

    Mechanics schrieb:

    eine öffentliche Iterator Klasse gibt und dafür verschiedene Funktonen, um einen zu bekommen:

    Der Punkt geht an dich, hab ich nicht dran gedacht. Zumal es ja eine (wenn auch abstrakte) Basisklasse für die Iteratoren gibt.

    Mechanics schrieb:

    Atomare Operationen sind nicht soo billig

    Was meinst du damit? Direkt auf die Node-Member zugreifen? Ist problemlos möglich, aber ist das nicht etwas unschön/unsauber?

    Mechanics schrieb:

    std::vector<std::shared_ptr<Node>> myChildren;
    Da muss ich mich erst recht fragen, warum du irgendwelche Sentinel brauchst.

    Die nodes in dem vector sind nicht zwangsläufig in der Reihenfolge, in der sie am Node hängen. Wegen den sentinels siehe oben 🙂

    Mechanics schrieb:

    remove sollten normalerweise einen Iterator auf das nächste Element zurückgeben und nicht auf das vorherige.

    Danke für den Hinweis, werds anpassen.

    Mechanics schrieb:

    std::shared_ptr<placeholder> myValue = nullptr;
    Auf den ersten Blick... Warum hält der Node den Wert als shared_ptr? Er müsste doch eigentlich die Ownership haben?

    Öhhh das stammt noch aus dem Code von vor 4 Monaten. Hatte glaube ich bei irgendwas Probleme mit dem unique-ptr und habs jetzt schlicht übersehen.

    Mechanics schrieb:

    Bei internen Klassen würde ich nicht so viele Getter und Setter machen, das ist eher Boilerplate.

    Also direkt auf die Member zugreifen?

    Vielen Dank für die vielen Hinweise und die Mühe die du schon in mein kleines Projekt gesteckt hast 🙂
    Jipie, ich lerne endlich mal, wie anständiger Code auszusehen hat 😃



  • Cherup schrieb:

    Mechanics schrieb:

    Atomare Operationen sind nicht soo billig

    Was meinst du damit? Direkt auf die Node-Member zugreifen? Ist problemlos möglich, aber ist das nicht etwas unschön/unsauber?

    GotW #91 Solution: Smart Pointer Parameters



  • Ok,

    ich weiß jetzt, das atomare Funktionen Funktionen sind die bei der Ausführung nicht unterbrochen weren. Verstehe ich noch nicht zu 100% weil ich noch nicht direkt mit multithreading zu tun hatte aber Ok.

    Ich verstehe allerdings den Zusammenhang mit meinem Code noch nicht bzw. bin mir unsicher. Beziehst du dich auf einzeiler Funktionen? Und/oder auf helper Funktionen wie LinkNodes() der Node Klasse?

    Die sind nicht wirklich notwendig, das ist mir bewußt, ich hatte sie eher aufgrund von Code-Redundanz und einer besseren Verständlichkeit des Codes geschrieben.

    Nochmals vielen Dank an alle, die mir hier helfen und sich damit beschäftigen wollen, ich freue mich da ungemein drüber 🙂

    Noch ein PS:
    Ich überlege mir grade, wie ich remove(first,last) am besten schreibe.
    Dabei ist mir ein mehr oder weniger kleines Problem aufgefallen. Was passiert, wenn first und last vertauscht sind? Also wenn der last VOR dem first node ist. Sollte ich da eine Prüfung einbauen oder dem Benutzer die korrekte Benutzung überlassen und einen Fehler riskieren?
    Ich neige dazu immer alles möglichst abzusichern und von einem DAU ("Dümmster anzunehmender User") auszugehen 😃

    Und noch eine andere Frage stellt sich mir:
    Wenn ich den shared_ptr eines Nodes lösche wird er ja zerstört. Was aber passiert, wenn dieser Node auch shared_ptr besitzt? Wird der Node dann trotzdem zerstört und die nodes der shared_ptr ebenfalls? Aus dem Bauchgefühl würde ich sagen ja bin aber nicht sicher.



  • Cherup schrieb:

    Warum die SentinelNodes? ...

    Gefällt mir trotzdem nicht. Bzw., mir ist auch nicht ganz klar, wie das implementiert ist... Ich hab im Source gesucht, und es gibt nicht viele Stellen, wo "Sentinel" vorkommt. Sind es einfach ganz normale Knoten? Warum ist es dann eine eigene Klasse, die von Node abgeleitet ist? Die scheint nichts zu machen.

    Cherup schrieb:

    Zumal es ja eine (wenn auch abstrakte) Basisklasse für die Iteratoren gibt.

    Besser wärs evtl. wieder mit Type Erasure.

    Cherup schrieb:

    Also direkt auf die Member zugreifen?

    Ja. Da das interne Objekte sind, bringt Kapselung nicht wirklich was. Außerdem ist es eh nicht wirklich gekapselt, wenn du für sowieso für alles getter und setter hast.

    Der Link von Swordfish passt doch perfekt zu deinem Code und deinen Fragen. Ich wollte darauf hinaus, dass shared_ptr als const& und nicht als Kopie übergeben werden sollte, und in dem GotW wird auch genau erklärt, warum. Das Inkrementieren und Dekrementieren des shared_ptr Counters ist atomar und damit mit bestimmten Kosten verbunden. Außerdem wird da auch erklärt, warum man Objekte besser als Referenzen oder Zeiger und nicht als shared_ptr übergibt. Weil es die Funktion idealerweise eben nichts angeht, wie die Argumente die als Parameter übergeben werden verwaltet werden.

    Ich weiß jetzt gar nicht, wie sich std::vector verhält, wenn die Iteratoren vertauscht sind. Hab ich noch nie ausprobiert und seh in der Doku auf den ersten Blick nichts dazu. Könnte mir vorstellen, dass es durch ein debug assert abgefangen wird und das wars dann.



  • Mechanics schrieb:

    Gefällt mir trotzdem nicht.

    Mir gefällts auch nicht wirklich, das ist irgendwie... unschön. Mir fällt aber auch keine elegantere Lösung ein um den start- bzw end-Punkt einer Reihe von Nodes festzulegen um ein stopkriterium für iterieren über Schleifen zu erhalten. Bzw. mir fällt gerade möglicherweise schon eine Lösung ein. Muss ich aber erstmal durchdenken. Könnte in etwas "komplexeren" begin und end-Funktionen enden.

    Ja, die Sentinelnodes sind nur normale Nodes mit anderem Namen. Es gibt 2 oder 3 stellen im Code wo ich den Node-Typ abfrage um ein löschen der Sentinels oder ein durchlaufen durch einen Iterator zu verhindern.
    Wie gesagt, ganz glücklich bin ich nicht damit.

    Mechanics schrieb:

    Der Link von Swordfish passt doch perfekt zu deinem Code und deinen Fragen.

    Das stimmt, ich war nur unsicher worauf du dich beziehst 🙂 ich kannte den Begriff der atomaren Funktionen noch nicht.

    Mechanics schrieb:

    Besser wärs evtl. wieder mit Type Erasure.

    Werd sehen was sich tun lässt 😉



  • So, das meiste an Umbau ist erledig, im wesentlichen fehlt nur noch der Umbau auf eine öffentliche Iterator-Klasse. Intern dürfte sich dadurch nichts ändern, da eh immer mit der abstrakten Basisklasse tree_iterator gearbeitet wird. Nach außen sollte es aber aufgeräumter wirken.

    Bevor ich zu meiner eigentlichen Frage komme noch zwei Anmerkungen 😉

    Mechanics schrieb:

    Initialisierungen mit nullptr schauen komisch aus.

    Muss ich bei den Pointern zu den umgebenen Nodes aber machen. An etlichen Stellen wird abgefragt, ob sie Null sind bevor sie initialisiert werden. Das führt immer zu einem segmentation fault.

    Mechanics schrieb:

    std::shared_ptr<placeholder> myValue = nullptr;
    Auf den ersten Blick... Warum hält der Node den Wert als shared_ptr? Er müsste doch eigentlich die Ownership haben?

    Das ist auch soweit richtig. Das Problem ist, das ich um den Wert abfragen zu können den Pointer zu holder<T> casten muss. (Anmerkung: template<typename T> class holder : public placeholder, um type erasure zu ermöglichen). Das geht aber nicht mit unique_ptr. Sicher, ich kann den Pointer mit release() freigeben, den Wert abfragen und wieder dem unique_ptr zuweisen. Aber ist das nicht ein deutliches Mehr an Arbeit als ein shared_ptr mit einem einzelnen cast?
    Wenn jemand eine bessere Zugriffsmöglichkeit kennt bitte verraten 🙂

    Zur eigentlichen Frage:
    Mir fällt es schwer ein sinnvolles Design für die Iteratoren zu finden und bevor ich wild drauf los schreibe frage ich besser mal nach.
    Ich möchte den tree_iterator zum einzigen öffentlichen Iterator machen. Daher ist meine Designe-Idee folgende: Alle Iteratoren erben wie bisher die meisten Funktionen des tree_iterator, nur die 4 increment/decrement-Operatoren sind ja bei jedem Iterator verschieden und werden deshalb virtuell.
    Die bisher öffentlichen unterschiedlichen Iteratoren werden innerhalb des tree_iterators gekapselt.
    Soweit so gut.
    Wobei ich Probleme habe ist zum einen in welchem scope sich der tree_iterator befinden soll, im namespace oder in der treeContainer-Klasse, die z.B. auch Node enthält.
    Zum anderen, von wem ich die Iteratoren per Funktion bekomme? Vom tree_iterator selbst oder vom treeContainer? type erasure wird nach meinem Verständnis dadurch erreicht, das ja jeder Iterator von tree_iterator erbt und tree_iterator die in/decrement-Operatoren an die entsprechende Iterator-Klasse weiterleitet. Wenn ich also immer nur tree_iterator zurückgebe ist es ja egal, welcher Iterator da jetzt wirklich hintersteckt.
    Dem tree_iterator gebe ich zwei Konstruktoren: Einen tree_iterator(), um einfach einen "leeren" Iterator mit "tree_iterator it()" erstellen zu können und einen tree-iterator(const tree_iterator& it), der den tree_iterator eben mit einem entsprechenden iterator initialisert.

    Ist das Design sinnvoll?
    Irgendwie steh ich total auf dem Schlauch...



  • Du musst einen shared_ptr nicht initialisieren, das macht sein Konstruktor. Deswegen ist es sinnlos, einem shared_ptr als Initialisierung nullptr zuzuweisen.

    Was du mit den Casts und unique_ptr meinst, versteh ich nicht. Den Zeiger kannst du dir mit get rausholen und casten.

    Der Vorteil von Type Erasure ist, dass man Objektsemantik bekommt/behält. Dann kann man mit dem Iterator als Objekt arbeiten. Sonst bräuchte man ja irgendeine Art von Zeigern, um polymorphes Verhalten zu ermöglichen. Und das ist bei einem Iterator nicht schön.
    Wenn du das so ähnlich wie in der STL machst, wäre der Iterator eine Unterklasse des TreeContainers.
    Macht es Sinn, einen ungültigen Iterator zu erstellen? Ich denke, nicht. Und die Klassen müssen irgendwie unterschiedlich heißen, sonst baust du einen Copy Constructor 😉



  • Mechanics schrieb:

    Du musst einen shared_ptr nicht initialisieren, das macht sein Konstruktor.

    Hab ich auch rausgenommen, nur die raw-pointer werden noch initialisiert, dachte du beziehst dich auch auf die.

    Mechanics schrieb:

    Den Zeiger kannst du dir mit get rausholen und casten.

    Ähmmm, ja... *hust* *hoch rot anlauf* Jemand möge mich schlagen, soviel dummheit gehört bestraft!!!

    Irgendwie versteh ich das mit den Iteratoren immer noch nicht, bin aber auch recht müde und ziemlich verwirrt... 🙄
    Gut, tree_iterator als public Klasse in den treeContainer packen. Und die restlichen? Als private Klassen in den treeContainer packen? Und wenn man einen Iterator anfordert, z.B. mit
    "treeContainer::tree_iterator it(Container->begin());"
    wird durch die begin()-Funktion ein node_iterator zurückgegeben, der durch die Vererbung bzw. durch den Rückgabetype von begin() nach außen als ein tree_iterator erscheint?

    Ansonsten schonmal der Changelog bis jetzt:
    treeContainer:
    - Zusammenfassung der RemoveToStart/RemoveToEnd-Funktionen zu einer remove(begin,end)-Funktion
    - Ersetzen von find(tree_iterator pos, T value) für die Suche innerhalb eines Nodes durch ranged find(tree_iterator first, tree_iterator last, T value)
    - Verbesserung/Vereinfachung der find()-Funktionen
    - Anpassung der begin/end-Funktionen wegen des Entfernens der SentinelNodes

    Node:
    - Entfernung der SentinelNodes
    - Entfernung der get/set-Funktionen für die Nodes

    Iteratoren:
    - Verbesserung der increment/decrement-Funktion der Iteratoren

    Ja, es hat geklappt die doofen Sentinels wegzubekommen 😃



  • Cherup schrieb:

    Als private Klassen in den treeContainer packen? Und wenn man einen Iterator anfordert, z.B. mit
    "treeContainer::tree_iterator it(Container->begin());"
    wird durch die begin()-Funktion ein node_iterator zurückgegeben, der durch die Vererbung bzw. durch den Rückgabetype von begin() nach außen als ein tree_iterator erscheint?

    Ich sehe den iterator im Moment am ehesten als einen Wrapper um sowas wie eine iterator_impl Klasse, die wiederum virtuelle Funktionen hat. Der Benutzer bekommt immer einen Iterator, die interne Implementierung unterscheidet sich aber.



  • Jetzt bin ich endgültig verwirrt, ich raffs einfach nicht 😃
    Wie stehen denn dann die Klassen tree_iterator, Iterator_impl und die restlichen zueinander? wer erbt von wem? Wieso hat nicht der tree_iterator die Implementierung der meisten Befehle und leitet nur die increment/decrement-Operatoren über virtuelle Funktionen an die eigentlichen Iterator-implementierungen weiter? Außer den genannten Operatoren unterscheiden die sich doch nicht?
    Ein Wrapper ist doch in dem Fall nichts anderes als eine Basisklasse, von der alle erben und die die virtuellen Funktionen weiterleitet, warum also durch eine weitere Klasse weiterleiten? Und wenn nur die Iterator_impl alle Funktionen hat, wie kann ich dann von außen auf die Funktionen zugreifen?

    Kannst du mir einmal kurz die Abhängigkeiten schreiben? Ich glaube, dann würde ich es verstehen...



  • Ich glaube, ich habe deinen Vorschlag jetzt besser verstanden. Falls mein Verständnis richtig ist, ist es recht elegant und sauber. Gestern war ich wie vernagelt 😃

    Ich habe jetzt alle Iterator-Klassen im treeContainer. tree_iterator ist der Wrapper und public, der rest private.
    iterator_impl ist neu dazugekommen und ist jetzt die Basisklasse aller Iteratoren-Typen. Iterator_impl nimmt die Stellung ein, die tree_iterator vorher hatte. Bis auf die increment/decrement-Operatoren sind hier alle Funktionen implementiert, die 4 Operatoren werden als virtuelle Funktionen weitergeleitet.

    Die tree_iterator-Klasse sieht aktuell so aus (Kommentare habe ich hier entfernt):

    class tree_iterator
        {
        private:
            tree_iterator(const iterator_impl& it) : iterator(it) {}
        public:
            tree_iterator(const tree_iterator& it) : iterator(it.iterator) {}
            ~tree_iterator() {}
    
            void operator =(const tree_iterator& rhs) [ iterator.operator =(rhs); }
            bool operator ==(const tree_iterator& rhs) { return iterator.operator ==(rhs); }
            bool operator !=(const tree_iterator& rhs) { return iterator.operator !=(rhs); }
            void Next() { iterator.Next(); }
            void Prev() { iterator.Prev(); }
            void First() { iterator.First(); }
            void Last() { iterator.Last(); }
            void Parent() { iterator.Parent(); }
            template<typename T> void setValue(T value) { iterator.setValue<T>(value); }
            template<typename T> T getValue() { return iterator.getValue<T>(); }
            std::type_index getValueType() { return iterator.getValueType(); }
            bool hasChildren() { return iterator.hasChildren(); }
    
            // Zum Rückgabetyp habe ich noch keine konkrete Idee
            tree_iterator& operator ++() { return iterator.operator ++(); }
            const tree_iterator operator ++(int junk) { return iterator.operator ++(junk); }
    
            virtual tree_iterator& operator --() { iterator.operator --(); }
            virtual const tree_iterator operator --(int) = 0;
    
        private:
            iterator_impl iterator;
    };
    

    Ist das soweit korrekt bzw. entspricht das deinem Vorschlag?

    Ich habe dazu noch eine Frage:
    Sind die Konstruktoren korrekt? Den privaten Konstruktor brauche ich, um dem Wrappen überhaupt eine iterator_impl zuweisen zu können. Alternativ könnte man auch einen tree_iterator() nehmen und den iterator_impl direkt zuweisen. Oder gibt es eine bessere Möglichkeit?
    edit:
    Ich vermute, das zumindest der private Konstruktor falsch ist. Im Moment scheint es, das immer nur eine iterator_impl Instanz gebaut wird. Damit funktioniert das iterieren natürlich nicht korrekt, da der operator nicht an die entsprechende Implementierung weitergeleitet wird.
    Außerdem wird der Pointer auf den Node verloren, sobald ich den Iterator an eine Funktion übergebe.

    Einen schönen (und hoffentlich sonnigen) Samstag
    Cherup



  • Ja, das kommt schon in etwa hin.
    Den iterator_impl musst du aber wahrscheinlich als Zeiger reinreichen und verwalten (bzw. als smart pointer). Der hat muss ja virtuelle Funktionen haben.



  • Das war ein vorläufiger Implementierungsvorschlag, damit ichs von anfang an richtig mache 🙂 In der Art habe ich es jetzt auch geschrieben.
    Mit dem Pointer hast du natürlich Recht, anders geht es nicht.
    Ich habe aber grad ein kleines Problem. An sich nichts wirklich schweres, aber ich suche eine elegante Lösung.

    Und zwar wird in den Funktionen von tree, die mir ja Iteratoren zurückgeben, kein neuer Iterator gebaut. (In den entsprechenden Funktionen wird z.B. ein node_iterator verwendet und erst bei return einem tree_iterator zugewiesen.) Soweit auch Ok. Es ist nur problematisch, wenn ich z.B. von Außen auf den Container zugreife und mir über find() 2 Nodes raussuchen lasse und sie zwei tree_iteratoren zuweise, um an diesen Nodes neue Nodes anzuhängen. Beide tree_iteratoren zeigen dann auf den selben Node, weil der interne node_Iterator der selbe ist.

    Meine Idee ist also, im Konstruktor des tree_iterators mit make_shared<>() einen neuen Iterator zu erstellen.
    Jetzt ist die Frage, wie ich am besten einen Iterator vom gleichen Typ wie den übergebenen erstelle.
    Man könnte den Type des übergebenen Iterators abfragen und z.B. über switch-case die entsprechende make-shared<>()-Funktion aufrufen, aber mir erscheint das... unsauber. Welche besseren Möglichkeiten gibt es?

    Oder wäre es eleganter, das der Konstruktor einen shared_ptr annimmt und in den tree-Funktionen selbst ein shared_ptr erstellt und an den tree_iterator übergeben wird?



  • Warum verwendest du eigentlich shared_ptr? unique_ptr wäre doch eher angebracht, da der baum der alleinige besitzer ist.



  • An sich ist eine Instanz des tree_iterators der Besitzer. Es soll für jede Instanz eines tree_iterators ein neuer entsprechender Iterator erzeugt werden.
    Grundsätzlich hast du wahrscheinlich Recht, das habe ich auch schon überlegt.
    Aber da ich aus irgendwelchen Gründen mit smart_pointern auf Kriegsfuss stehe, möchte ich es erstmal mit shared_pointern zum funktionieren bekommen 😃
    Danach werde ich mich um Feinheiten wie eben den unique_ptr kümmern. Das bedeutet zwar ein Mehr an Arbeit aber die Frustration aufgrund von ständigen Fehlern verringert sich 😉



  • So,
    der große Iterator-Umbau ist abgeschlossen.

    Hier der Changelog seit der letzten Version

    tree:
    Zusammenfassung der RemoveToStart/RemoveToEnd-Funktionen zu einer remove(begin,end)-Funktion
    Ersetzen von find(tree_iterator pos, T value) für die Suche innerhalb eine Nodes durch ranged find()
    Anpassung der begin/end-Funktionen wegen des Entfernens der SentinelNodes
    Funktionen zum Erstellen eines tree_iterators eines bestimmten Iteratortyps

    Node:
    Entfernung der SentinelNodes
    Entfernung der get/set-Funktionen für die Nodes
    Value ist unique_ptr statt shared_ptr
    Anpassungen bei der Übergabe von shared_pointern

    Iteratoren:
    Verbesserung der increment/decrement-Funktion der Iteratoren
    tree_iterator als einziger öffentlicher Iterator
    tree_iterator als Wrapper für Iteratoren
    Implementierung der abstrakten Klasse iterator_impl als neue Basisklasse für die Iteratoren
    eigenständige Implementierung der const-Iteratoren

    API/Beschreibung/Sonstiges:
    Aktualisierung der API aufgrund der Änderungen
    Node-Beschreibung jetzt in der Node-Klasse
    Umbenennen der Klasse Node in _Node
    Umbenennen der Iteratoren in _(Iterator)

    Den Code findet ihr unter
    http://pastebin.com/Ss9ABgm5 (treeContainer.h)
    http://pastebin.com/6MTz3and (treeCOntainer.cpp)

    Ich hoffe, ich habe nichts wesentliches mehr vergessen 😉

    Schönen Sonntagnachmittag
    Cherup



  • Ich hab irgendwie das Gefühl, dass bei den Iteratoren noch zu viele Heap Operationen im Spiel sind, das ist nicht so effizient. Evtl. könntest du das noch schauen, ob du was optimieren kannst.
    So eine Idee... Was ist an dem Iterator eigentlich genau dynamisch? Vielleicht könnte man das Verhalten und die Daten so ein bisschen von einander trennen und dann doch wieder mit Objekten anstatt mit Zeigern arbeiten.
    Sind aber eher Microoptimierungen, lohnt sich vielleicht auch nicht wirklich.



  • So wenig Anmerkungen? Das freut mich 🙂
    Ja an einigen Stellen gibt es sicherlich noch Optimierungsmöglichkeiten.

    Mechanics schrieb:

    bei den Iteratoren noch zu viele Heap Operationen im Spiel sind

    Was meinst du mit Heap-Operationen?

    Mechanics schrieb:

    Was ist an dem Iterator eigentlich genau dynamisch?

    Wie meinst du das?

    Mechanics schrieb:

    Vielleicht könnte man das Verhalten und die Daten so ein bisschen von einander trennen

    Das Verhalten und die Daten sind doch getrennt? Abgesehen davon, das die Iteratoren einen Node-Zeiger haben (was sie ja auch brauchen) habe die Iteratoren doch nichts mit den eigentlichen Daten zu schaffen?

    Ansonsten würde ich sagen, das der Container vorläufig fertig ist 🙂
    Danke nochmal für die viele Mühe.

    Viele Grüße
    Cherup


Anmelden zum Antworten