wiederverwendbare iteratoren schablonen?



  • Hallo Leute.

    Mich nervt es, dass ich bei jeder Klasse die iteratoren hat, die from scratch neu schreiben muss. (OK, ein bisschen das Copy&Paste AntiPattern verwenden und es wird erträglich - aber hässlich ist es dennoch).

    Meine Idee Nr. 1:
    Makros vergewaltigen:

    #define FORWARD_ITERATOR(name, value, deref, inc) \
    class name { \
      value val; \
      ... \
      name& operator++() {\
        inc; \
        return *this; \
      } \
      ... \
      value& operator*() { \
        return deref; \
      } \
      ... \
    };
    
    alles in allem also nicht schön.
    
    Nummer 2:
    Ein Template verwenden
    [cpp]
    template<class Impl>
    class BasicForwardIterator
    {
      Impl impl;
    
      BasicForwardIterator& operator++()
      {
        impl.inc();
        return *this;
      }
    
      Impl::value_type& operator*()
      {
        impl.deref();
      }
      ...
    };
    

    Man müsste dann immer nur einen minimalen 'iterator' schreiben, der inc(), dec(), deref() und compare() anbietet und könnte damit einen fertigen Iterator erhalten.

    Der große Nachteil dieser beiden Varianten ist aber, dass man erstmal für alle möglichen iteratoren konzepte eine Vorlage schreiben müsste. Da würde ich aber auch gerne etwas Code wiederverwenden.
    Denn zB
    ForwardIterator und ConstForwardIterator unterscheiden sich lediglich dadurch, dass die Const Variante den op* und op-> nur als const und nicht auch als non-const anbietet. irgendwie muss man da doch den code wiederverwenden können, oder? (ich meine: ein bidirectional iterator ist zB sowohl ein forward als auch ein backward iterator - da denke ich an vererbung, aber ich weiss nicht, wie man diese implementieren soll? der op++ liefert ja by value - und muss daher den richtigen typen kennen...)

    wie macht ihr das mit den iteratoren?



  • erstmal vorneweg: wenn impl in deinem 2. ansatz eine policy ist, solltest du dem liskov prinzip zum trotz ruhig davon erben,stichwort löschender iterator. natürlich muss es dann auch einen ctor geben, der einen Impl& annimmt(zusätzlich zum default/copy ctor)

    zum thema const/nonconst: ein bool template parameter der dir die const/nonconst entscheidung abnimmt, sollte nützlich sein, dann könntest du zb sowas machen:

    Impl::Type<isConst>::Value& operator*()
    {
        impl.deref();
    }
    

    dadurch musst du schon weniger schreiben.

    (ich meine: ein bidirectional iterator ist zB sowohl ein forward als auch ein backward iterator - da denke ich an vererbung, aber ich weiss nicht, wie man diese implementieren soll? der op++ liefert ja by value - und muss daher den richtigen typen kennen...)

    das hauptproblem bei der vererbung ist, dass man die basisklassen nicht synchron bekommt, dh die verwendung des op++ der forward iterator baseclass sich nicht die position der backward iterator baseclass auswirkt. am einfachsten ist es wohl, dafür eine eigene Klasse zu schreiben



  • otze schrieb:

    erstmal vorneweg: wenn impl in deinem 2. ansatz eine policy ist, solltest du dem liskov prinzip zum trotz ruhig davon erben,stichwort löschender iterator. natürlich muss es dann auch einen ctor geben, der einen Impl& annimmt(zusätzlich zum default/copy ctor)

    Löschender iterator sagt mir jetzt nichts.

    Impl::Type<isConst>::Value& operator*()
    {
        impl.deref();
    }
    

    dadurch musst du schon weniger schreiben.

    Leider geht dann
    const const_iterator f;
    f;
    nicht.
    Nicht dass ich das wollen würde, aber diese einschränkung ist ein bisschen lästig - weil der op
    ja das objekt nicht verändert, wenn es eine const_ref returned...

    das hauptproblem bei der vererbung ist, dass man die basisklassen nicht synchron bekommt, dh die verwendung des op++ der forward iterator baseclass sich nicht die position der backward iterator baseclass auswirkt.

    Das wäre ja kein Problem - weil es Impl ja nur einmal gibt...
    und op++ mach impl.inc() und op-- macht impl.dec()
    allerdings slice ich mir den iterator leider dauernd weg - nämlich immer dann wenn ich beim op++ per value returne - was ich ja bei postincrement machen muss 😞



  • Löschender iterator sagt mir jetzt nichts.

    es gibt verschiedene iterator konzepte, eins davon ist, dass der iterator das objekt auf welches er zeigt, löschen kann.

    Leider geht dann
    const const_iterator f;
    *f;
    nicht.

    schei** const-correctness 👎

    najut, dann anders:

    Impl::Type<isConst>::Value& operator*()
    {
        impl.deref();
    }
    Impl::Type<true>::Value& operator*() const
    {
        impl.deref();
    }
    

    beim (const_)iterator wird die am besten passende version rausgesucht,und beim const (const_)iterator wird nur die zweite version in betracht gezogen.

    Das wäre ja kein Problem - weil es Impl ja nur einmal gibt...

    Impl gibt es 2x, weil der forwarditerator anders funktioniert als der backwarditerator. natürlich könntest du sagen, dass impl immer forward und backward können muss, aber wozu brauchst du dann noch ne unterscheidung zwischen den iteratoren? und wenn du dann auch noch meinem ersten rat folgen würdest, und vererben wolltest, dann müsstest du sogar virtual vererbung benutzen, und das ist ja eigentlichw as, was man vermeiden sollte...



  • Nur mal so nebenbei, aber gibt es im Standard nicht schon 'ne ganze Reihe von vorgefertigten Iterator Klassen. Hast du da schon mal nachgeschaut, ob was passendes dabei ist?



  • @Shade
    Ist boost::iterator hier nicht hilfreich? Wenn nicht direkt die Lib, dann vielleicht die Konzepte.



  • otze schrieb:

    erstmal vorneweg: wenn impl in deinem 2. ansatz eine policy ist, solltest du dem liskov prinzip zum trotz ruhig davon erben,stichwort löschender iterator. natürlich muss es dann auch einen ctor geben, der einen Impl& annimmt(zusätzlich zum default/copy ctor)

    geh darauf mal nöher ein. wozu benutzt man so einen iterator und warum zwingt mich das zum erben?



  • HumeSikkins schrieb:

    @Shade
    Ist boost::iterator hier nicht hilfreich? Wenn nicht direkt die Lib, dann vielleicht die Konzepte.

    😮
    Man lernt in boost dauernd etwas neues.
    Danke!
    Ich hatte immer gedacht boost::iterator erweitert die iteratoren um verschiedene Konzepte.
    👍

    groovemaster schrieb:

    Nur mal so nebenbei, aber gibt es im Standard nicht schon 'ne ganze Reihe von vorgefertigten Iterator Klassen. Hast du da schon mal nachgeschaut, ob was passendes dabei ist?

    Was meinst du?



  • volkard schrieb:

    otze schrieb:

    erstmal vorneweg: wenn impl in deinem 2. ansatz eine policy ist, solltest du dem liskov prinzip zum trotz ruhig davon erben,stichwort löschender iterator. natürlich muss es dann auch einen ctor geben, der einen Impl& annimmt(zusätzlich zum default/copy ctor)

    geh darauf mal nöher ein. wozu benutzt man so einen iterator und warum zwingt mich das zum erben?

    soweit ich das hier verstanden hab, geht es um ein möglichst flexibles iteratorgrundgerüst.
    Ein normaler iterator braucht ja eigentlich nur den op++/op-- und dann die methoden zur dereferenzierung der objekte auf welches er zeigt. jegliches mehr/bzw weniger würde dem konzept ansich schaden. Nun will aber ein benutzer einen iterator haben, der auch löschen kann. würde Impl nicht vererbt werden, so bräuchte er ein völlig neues gerüst für den iterator, durch die vererbung kann er Impl so benutzen:

    template<class T>//typ der objekte im container
    class Impl{
        protected:
        //hier die ganzen standardfunktionen, die jedes Impl objekt haben muss
        public:
            //weitere methoden für den iterator
            void destroy(){/*...*/}
    };
    

    dh ein iterator<Impl> hätte nun auch eine methode um das objekt, auf welches er grad zeigt, zu löschen. Alternativ könnte er noch einige andere operatoren überladen, wie zb den op== um mit bestimmten anderen iteratoren kompatibel zu sein, usw.

    //ein ganz einfaches beispiel
    for(Iterator<Impl<Foo> > i=container.begin();i!=container.end();++i){
        if(i->getStatus()==bad)
            i->destroy();
    }
    


  • Shade Of Mine schrieb:

    groovemaster schrieb:

    Nur mal so nebenbei, aber gibt es im Standard nicht schon 'ne ganze Reihe von vorgefertigten Iterator Klassen. Hast du da schon mal nachgeschaut, ob was passendes dabei ist?

    Was meinst du?

    Ich hab mich zwar noch nicht näher damit befasst, was ich aber in näherer Zukunft wohl machen werde, aber im Standard gibt es ein ganzes Kapitel nur über Iteratoren. ( ➡ 24 Iterators library)


Anmelden zum Antworten