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



  • 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



  • Heap Operationen sind new und delete. Die sind relativ langsam und es wäre eigentlich nicht schlecht, wenn man die vermeiden könnte.

    Ich meinte, dass bei den Iteratoren wahrscheinlich das Verhalten dynamisch ist, die Daten aber wahrscheinlich für alle Iteratoren gleich sind (ein Node Zeiger). Korrigiere mich, wenn ich falsch liege.
    Und wenn die Daten eh statisch sind, könnte man doch eigentlich auf new/delete und Zeiger verzichten. Die einfachste Möglichkeit wäre ein enum IteratorStrategy und dann ein switch case. Oder ein Struct mit Funktionszeigern, die einen Zeiger auf die Daten bekommen.

    Was mir nämlich konkret nicht so gefällt ist, dass Operatoren wie ++ Heap Operationen benötigen. Wenn man die API etwas ändern würde, könnte man darauf wohl verzichten, in dem der iterator_impl einfach geändert wird. Aber da die Operatoren noch einen anderen Iterator zurückgeben müssen, und das Verhalten der Iteratoren dynamisch ist, braucht man eben Heap Operationen. Deswegen der Vorschlag mit der Optimierung.



  • Mechanics schrieb:

    die Daten aber wahrscheinlich für alle Iteratoren gleich sind (ein Node Zeiger). Korrigiere mich, wenn ich falsch liege.

    Damit liegst du richtig.

    Allerdings sind die einzigen Operatoren, die Heap-Funktionen brauchen die Post_increment und Post-decrement Operatoren.
    Ich habe bis jetzt keinen Implementierungsvorschlag gesehen, bei dem das anders wäre, daher wüßte ich nicht, wie man das durch eine Änderung an der API beseitigen könnte.

    Soweit ich das beurteilen kann, kommen die heap_operationen sonst fast nur bei Funktionen zum Einsatz, bei denen auch ein neuer Iterator zurückgegeben werden soll.
    Ok, es gibt die eine oder andere Funktion, die ich aus einer anderen Funktion aufrufe und deshalb neue Iteratoren erstellen muss, das betrifft z.B. die clear()-Funktionen. Da geschieht das aber nur einmal. So ganz glücklich bin ich damit noch nicht. Auf der anderen Seite müsste ich sonst entweder neue private Funktionen erstellen (was ich nicht möchte) oder Funktionen redundant implementieren, was eine Änderung/Optimierung erschweren kann und fehleranfälliger ist.

    Mechanics schrieb:

    Und wenn die Daten eh statisch sind, könnte man doch eigentlich auf new/delete und Zeiger verzichten.

    Geb ich dir Recht mit. Das würde bedeuten, das man nur einen einzigen iterator (den tree_iterator) hat, der aber verschiedene Iteratortypen simuliert, korrekt?



  • Cherup schrieb:

    Geb ich dir Recht mit. Das würde bedeuten, das man nur einen einzigen iterator (den tree_iterator) hat, der aber verschiedene Iteratortypen simuliert, korrekt?

    Ja, das wäre die etwas plumpere Lösung als die jetzige, die sich aus Performancegründen aber vielleicht lohnen könnte. Wobei das eher theoretische Überlegungen sind 😉 Premature optimization ist the root of all evil und so 😉 Andererseits erwarte ich von so grundlegenden Klassen wie Containern, dass sie auf Hinsicht auf Performance möglichst vernünftig implementiert sind und nicht unnötig Zeit verschwenden.
    Eine andere Möglichkeit wäre wie gesagt ein Struct mit Funktionszeigern. Also, die virtual function pointer table von Hand nachbauen, dafür ist dein Iterator dann nur ein Objekt konstanter Größe.
    Und wo ich das schreibe, fällt mir noch was ein. Wenn deine Implementierungsklasse keine Daten halten würde, sondern nur Paramter reinbekommen würde (die evtl. verändert werden) und der tree_iterator die Daten hält, könnte man den Zeiger auf die Implementierungsklasse einfach rumreichen und müsste das Objekt nicht mit new clonen.



  • Mechanics schrieb:

    Wenn deine Implementierungsklasse keine Daten halten würde, sondern nur Paramter reinbekommen würde (die evtl. verändert werden) und der tree_iterator die Daten hält, könnte man den Zeiger auf die Implementierungsklasse einfach rumreichen und müsste das Objekt nicht mit new clonen.

    Die Idee finde ich richtig gut. Damit hätte man eine gute Kombination aus Performance und einer sauberen Implementierung.
    Außerdem würden sich die Änderungen am Code in Grenzen halten 😃
    Ich habe wenig Lust die ganzen Iteratoren wieder zu entfernen nachdem ich da viele Stunden reingesteckt habe 😃



  • Die Änderungen am Iterator sind fertig.

    Hier ein kleiner Changelog:

    Iteratoren:
    tree_iterator hält jetzt den Pointer auf den Node.
    Pointer auf den Node werden an die iterator_impl-Klassen weitergereicht.
    post-in/decrement sind aus den iterator_impl-Klasse geflogen
    Umbennung der Operatoren in den iterator_impl-Klassen, nötig da operatoren keine Argumente annehmen.
    Privater Konstruktor des tree_iterators angepasst.

    Node:
    Entsprechende Funktionen geben jetzt einen _Node* statt eines iterators zurück

    tree:
    Anpassungen in den Funktionen wegen der Änderung am tree_iterator.
    tree hält jetzt shared_pointer auf die iterator_impl-Klassen.
    shared_pointer auf die iterator_impl-Klassen werden nur an den tree_iterator gereicht, es werden keine neuen Instanzen mehr erzeugt.

    Die Implementierung der Navigations-Funktionen des Iterators (Next(), Prev() usw) habe ich bewußt in der iterator_impl Klasse gelassen. Damit ist die Implementierung von dem tree_iterator getrennt. Es macht zwar nicht viel, aber es wirkt für mich sauberer und einheitlicher.

    Den überarbeiteten Code findet ihr unter
    http://pastebin.com/CKzYuD2S (treeContainer.h)
    http://pastebin.com/7dfMRKe4 (treeContainer.cpp)

    Wenn noch was auffällt bitte melden 🙂

    Viele Grüße
    Cherup



  • Hallöchen nochmal,

    mir ist bei der Benutzung tatsächlich noch was aufgefallen.
    Und zwar folgendes:
    Es kann ziemlich nervig werden, wenn man mehrere (mehr als 3 oder 4) neue Nodes in den Baum einfügen möchte, vor allem wenn es auch noch eine bestimmte Struktur in den anzuhänden Nodes geben soll.

    Beispiel:
    An einen Node sollen 3 Knoten angefügt werden, alle Nodes sollen 3 weitere Childnodes bekommen und der erste Childnode vom ersten ChildNode soll wieder 2 bekommen.
    Das würde also so aussehen:

    O
               /   |   \
              /    |    \
             O     O     O
            /|\   /|\   /|\
           O O O O O O O O O 
          / \
         O   O
    

    Das macht zusammen 14 Knoten, die angefügt werden sollen, das sind mindestens 14 Befehle und 3 Iteratoren... Nicht sehr benutzerfreundlich.

    Daher habe ich mir folgendes überlegt:
    Da es ja die schöne Möglichkeit der Variadic Templates gibt nutze ich sie auch.
    Ich schreibe also eine neue Funktion template<typename... T> createMultiple(), die beliebig viele Nodes erzeugen kann.
    Schön und gut, aber ich will ja eine Struktur in den Nodes!
    Da man keine zwei expansion packs an eine Funktion übergeben kann brauche ich dazu eine weitere interne öffentliche Klasse, die Strukturen speichern und die ich dann der createMultiple()-Funktion mit übergeben kann.
    So weit so gut, wie übergebe ich der NodeStructure-Klasse jetzt die Struktur der Nodes? Wieder mit einer Variadic Template-Funktion und mit einem relativ einfachen Schema:
    Man übergibt einfach die Anzahl der Nodes, die an jeden Node angehängt werden soll in der Reihenfolge der anzuhängenden Nodes.
    Die erste Zahl steht also für die Anzahl der anzuhängen Nodes an den ParentNode, die 2. für die Anzahl der anzuhängen Nodes an den ersten ChildNode usw.

    Für das obige Beispiel würde das dann so aussehen:
    [3, 3, 2, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0]

    Gut, bei diesem Beispiel ist der Schreibaufwand noch realtiv gering, aber wenn ich z.B. 50 Nodes an ein einen der ChildNodes hängen möchte wirds unschön...
    Auch da habe ich mir was überlegt. Man kann auch eine negative Zahl übergeben, die anzeigt an wieviele der nächsten Nodes kein Node angefügt werden soll. [..., -10,...] bedeutet also nichts anderes als 10 mal 0.
    Die Zahlenfolge wäre dann z.b.
    [3, 3, 2, -2, 3, -3, 3, -3]

    Zusätzlich zu Zahlen sollen auch vector<int> und NodeStructures als Parameter möglich sein.

    Die createMultiple()-Funktion würde also am Ende so aussehen:

    template<typename... T> void createMultiple(const tree_iterator& pos, const NodeStructure& NStruc, T... Args);
    

    Auch die "normale" createNode()-Funktion würde ich zu einem variadic template umschreiben, um mehrere Nodes auf einmal an einen Node anhängen zu können.

    Jetzt die Frage:
    Was haltet ihr davon? Ist das sinnvoll? Gibts Verbesserungen?

    Cherup


Anmelden zum Antworten