Zwei Design Fragen bei Operatorüberladung



  • Dravere schrieb:

    Zu random_access_iterator_helper :
    Ihr vergesst, worum es hier geht. PtrIterator ist da, um einen normalen Iterator zu kapseln, ähnlich wie ein reverse_iterator . Daher müssen operator *() und operator ->() spezialsiert werden, so dass sie den zurückgegebenen Typ nochmals dereferenzieren. Keine Chance sowas mit random_access_iterator_helper hinzubekommen, zumindest habe ich keine sinnvolle gefunden

    Hallo Dravere,

    bei allen boost::*_iterator_helper-Konstruktionen muss operator*() selbst implementiert werden. operator->() wird aus operator*() generiert. Eine Kapselung eines Iterators, der als value_type einen T* hat, zu einem, der ein T als value_type hat, ist da wirklich eine leichte Übung:

    #include <iostream>
    #include <vector>
    #include <boost/operators.hpp>
    #include <boost/type_traits/remove_pointer.hpp>
    
    struct Foo
    {
        void bar() { std::cout << "bar gerufen .." << std::endl; }
    };
    
    template< typename T >
    class PtrIterator : public boost::random_access_iterator_helper< PtrIterator< T >, 
        typename boost::remove_pointer< typename std::iterator_traits< T >::value_type >::type >
    {
    public:
        typedef typename boost::remove_pointer< typename std::iterator_traits< T >::value_type >::type value_type;
    
        /* implicit */ PtrIterator( T base )
            : m_base( base )
        {}
        PtrIterator& operator++()
        {
            ++m_base;
            return *this;
        }
        value_type& operator*() const
        {
            return **m_base;
        }
        bool operator==( const PtrIterator& b ) const
        {
            return m_base == b.m_base;
        }
        PtrIterator& operator+=( std::ptrdiff_t off )
        {
            m_base += off;
            return *this;
        }
    
        // usw. operator--, operator-=
    
    private:
        T m_base;
    };
    
    int main()
    {
        using namespace std;
        vector< Foo* > v;
        v.push_back( new Foo );
    
        typedef std::vector< Foo* >::iterator Iter;
        typedef PtrIterator< Iter > PtrIter;
        for( PtrIter i = v.begin(); i != v.end(); ++i )
            i->bar();
    
        return 0;
    }
    

    .. funktioniert einwandfrei 🕶

    Dravere schrieb:

    Expression-Template-Technik? Wie es zum Beispiel Boost.Spirit oder Boost.Lambda machen?

    boost.ublas ist so gemacht .. das kommt dem am nächsten, was ich meine.

    Dravere schrieb:

    Glaub ich weniger, dass dies was bringen würde. Problem ist halt die teuren std::deque Kopien.

    Umso eher bringen die Expression-Template was - da fällt das Kopieren von temporären Kopien vollständig weg.

    Dravere schrieb:

    Ich erweitere aus Interesse noch die Frage 2:
    Wie exzessiv setzt ihr eigentlich die Copy&Swap Technik ein? Schreibt ihr jedesmal euren eigenen operator = , statt der vom Kompiler generierten?

    .. natürlich nur, wenn es notwendig ist. Ich setze Copy&Swap vornehmlich beim operator= ein, aber auch nur dann, wenn es 'swap'-bare Inhalte habe - also std::container oder ähnliches.

    Gruß
    Werner



  • Dravere schrieb:

    Ich erweitere aus Interesse noch die Frage 2:
    Wie exzessiv setzt ihr eigentlich die Copy&Swap Technik ein? Schreibt ihr jedesmal euren eigenen operator = , statt der vom Kompiler generierten?

    Copy&swap nur wenns was zu swappen gibt. Ggf. kann man auch die swap-baren member in ein struct kapseln, dessen op= entsprechend implementieren und den Rest dem compilergenerierten op= überlassen.


  • Administrator

    @pumuckl & Werner Salomon,
    Danke für die Erhellung. Ich habe einen riesigen Denkfehler gemacht. Aber dann wäre dies geklärt.

    Werner Salomon schrieb:

    boost.ublas ist so gemacht .. das kommt dem am nächsten, was ich meine.

    Kenne mich mit Boost.uBLAS überhaupt nicht aus. Werde es mir aber dann mal anschauen. Danke für den Hinweis.

    Werner Salomon schrieb:

    .. natürlich nur, wenn es notwendig ist.

    Und die Frage ist halt eben, wann ist es notwendig?
    Ich meine, Coyp&Swap wird doch in erster Linie dazu eingesetzt, um Exceptionsicherheit zu erzielen. Also keine ungültige Objekte zu erhalten, weil mittem im Kopiervorgang abgebrochen wurde.

    Wenn ihr nun davon ausgehen könnt, dass keine Exception geworfen werden kann, ihr aber trotzdem swap-bare Inhalte habt, was macht ihr dann? Geht ihr auf Nummer sicher, weil es vielleicht in Zukunft Änderungen geben könnte und dadurch Exceptions geworfen werden?

    Grüssli



  • Dravere schrieb:

    Zu random_access_iterator_helper :
    Ihr vergesst, worum es hier geht. PtrIterator ist da, um einen normalen Iterator zu kapseln, ähnlich wie ein reverse_iterator . Daher müssen operator *() und operator ->() spezialsiert werden, so dass sie den zurückgegebenen Typ nochmals dereferenzieren. Keine Chance sowas mit random_access_iterator_helper hinzubekommen, zumindest habe ich keine sinnvolle gefunden 😉

    Das erklärt wieso du boost::remove_pointer verwendest. Das hab' ich mich nämlich grad ca. 2 Minuten lang gefragt, bevor ich auf die gute Idee gekommen bin, mal die restlichen Beiträge zu lesen. (*)

    ---

    z.T. Lesbarkeit: er. Wie sag ich das jetzt. Ich würde es als "ausreichen lesbar" bezeichnen. Oder als "entzifferbar". Einfach mal schnell drüberlesen ist nicht drin, es sei denn man kennt die verwendeten Klassen schon gut. Ansonsten muss man zumindest halt machen um mal den einen oder anderen "educated guess" zu machen, was die Template-Klasse XYZ wohl machen wird. Allerdings würde ich sagen dass das vollkommen ausreichen ist, solange verständlich ist, was die Klasse tut. Dann muss ich den Code nämlich garnicht lesen können, es sei denn ich suche Fehler, und das braucht dann sowieso seine Zeit.

    *:
    Ich würde allerdings einen anderen Namen vorziehen. "PtrIterator" klingt für mich nach einem Iterator der mit Hilfe eines Pointers implementiert ist. Was du machst würde ich wohl eher "DereferencingIterator" nennen.

    Was += angeht: kommt drauf an denke ich. Wenn es dir reicht dass dein "PtrIterator" die selben Garantien bezüglich += abgibt wie der "underlying" Iterator, dann sollte ein einfaches weiterreichen an den "underlying" Iterator vollkommen ausreichen, so wie es Werners Code auch macht. Und dann wäre es IMO ziemlich sinnlos hier copy & swap zu verwenden -- selbst wenn es kein Performance-Problem gäbe.

    ---

    BTW: du könntest das ganze noch flexibler machen. Pack die "Transformation" (Dereferenzieren) in eine Traits-Klasse, und implementiere die "TransformingIterator" Klasse so, dass sie auch mit forward oder bidi Iteratoren funktioniert 😃
    Dann könnte man z.B. ganz einfach Iteratoren basteln, die das "first" eines std::pair zurückgeben. Oder einen vollkommen sinnfreien, aber sicher lustigen "ROT13" Iterator für Strings machen. Oder halt einfach irgendwas 😃



  • hustbaer schrieb:

    BTW: du könntest das ganze noch flexibler machen. Pack die "Transformation" (Dereferenzieren) in eine Traits-Klasse, und implementiere die "TransformingIterator" Klasse so, dass sie auch mit forward oder bidi Iteratoren funktioniert

    guter Hinweis, da hätte ich auch gleich drauf kommen können .. und wenn man jetzt noch weiß, dass boost sowas schon fix & fertig anbietet, wird's übersichtlich:

    #include <algorithm>
    #include <functional>
    #include <iostream>
    #include <vector>
    #include <boost/iterator/transform_iterator.hpp>
    
    struct Foo
    {
        void bar() { std::cout << "bar gerufen .." << std::endl; }
    };
    
    template< typename T >
    struct DeRef : std::unary_function< T*, T& >
    {
        T& operator()( T* p ) const
        {
            return *p;
        }
    };
    
    int main()
    {
        using namespace std;
        vector< Foo* > v;
        v.push_back( new Foo );
    
        typedef std::vector< Foo* >::iterator Iter;
        typedef boost::transform_iterator< DeRef< Foo >, Iter > PtrIter;
        const PtrIter iEnd( v.end() );
        for( PtrIter i( v.begin() ); i != iEnd; ++i )
            i->bar();
    
        // bzw.:
        for_each( boost::make_transform_iterator( v.begin(), DeRef< Foo >() ),
            boost::make_transform_iterator( v.end(), DeRef< Foo >() ),
            std::mem_fun_ref( &Foo::bar ) );
    
        // oder mal total einfach und ohne boost
        for_each( v.begin(), v.end(), std::mem_fun( &Foo::bar ) );
    
        return 0;
    }
    

    und wenn man noch bedenkt, dass die boost::bind-Konstrukte sowohl mit Pointern als auch mit Referenzen zusammenarbeiten (als Alternative zu std::mem_fun[ref]), dann braucht man so einen PtrIterator gar nicht mehr.

    Gruß
    Werner


  • Administrator

    hustbaer schrieb:

    Ich würde allerdings einen anderen Namen vorziehen. "PtrIterator" klingt für mich nach einem Iterator der mit Hilfe eines Pointers implementiert ist. Was du machst würde ich wohl eher "DereferencingIterator" nennen.

    Über die Namenswahl reden wir hier nicht ... 😃
    Ich bin ein grauenhaft schlechter Namenswähler. Sieht man auch sehr gut an den Namen meiner Programme ... 😉

    hustbaer schrieb:

    Dann könnte man z.B. ganz einfach Iteratoren basteln, die das "first" eines std::pair zurückgeben. Oder einen vollkommen sinnfreien, aber sicher lustigen "ROT13" Iterator für Strings machen. Oder halt einfach irgendwas 😃

    Das war aber nicht mein Ziel, mich hat nur dieses (*iter)-> gestört *g*
    Die Idee ist aber wirklich gut.

    Werner Salomon schrieb:

    .. und wenn man jetzt noch weiß, dass boost sowas schon fix & fertig anbietet, ...

    Was bietet Boost eigentlich nicht an? Ich habe das Gefühl ich lerne über Boost jedesmal was neues. Das ist noch schlimmer als mit C++ selbst 😉

    Werner Salomon schrieb:

    und wenn man noch bedenkt, dass die boost::bind-Konstrukte sowohl mit Pointern als auch mit Referenzen zusammenarbeiten (als Alternative zu std::mem_fun[ref]), dann braucht man so einen PtrIterator gar nicht mehr.

    Da liegst du falsch. Gut, ich hätte vielleicht irgendwie den einen Algorithmus umbauen können, so dass ich ihn über eine for_each Funktion nehmen kann, aber das wäre mir persönlich zu aufwendig gewesen. Manchmal tut es auch eine schöne altmodische for-Schleife. Man muss ja nicht immer auf Teufel komm raus die Algorithmen aus der Standardbibliothek einsetzen. Daher kann sowas schon sinnvoll sein. Wenn es auch nur zur "Verschönerung" dient. Ich wollte damit wirklich nur das (*iter)-> zu einem iter-> umwandeln. 😉

    Aber nochmals zurück zum Ursprung. Frage 1 wäre inzwischen wohl geklärt. Da hat meine 😮 Reaktion doch irgendwo was sinnvolles bewirkt und das Nachfragen war eine gute Idee 🙂

    Die Antworten auf Frage 2 reichen mir irgendwie noch nicht so ganz. Zum einen wäre eine Antwort auf diese Fragen noch nett:

    Dravere schrieb:

    Wenn ihr nun davon ausgehen könnt, dass keine Exception geworfen werden kann, ihr aber trotzdem swap-bare Inhalte habt, was macht ihr dann? Geht ihr auf Nummer sicher, weil es vielleicht in Zukunft Änderungen geben könnte und dadurch Exceptions geworfen werden?

    Auch begreife ich nicht ganz, wieso ihr kein Copy&Swap anwenden würdet, wenn es keine swap-baren Member hat. Oder arbeitet ihr dann einfach zuerst auf einer Kopie und weist die Member der Kopie dann einzeln zu, statt einen swap auszuführen? Ich meine, auch bei nicht swap-baren Inhalten kann eine Exception fliegen.

    Grüssli



  • Dravere schrieb:

    Dravere schrieb:

    Wenn ihr nun davon ausgehen könnt, dass keine Exception geworfen werden kann, ihr aber trotzdem swap-bare Inhalte habt, was macht ihr dann? Geht ihr auf Nummer sicher, weil es vielleicht in Zukunft Änderungen geben könnte und dadurch Exceptions geworfen werden?

    Auch begreife ich nicht ganz, wieso ihr kein Copy&Swap anwenden würdet, wenn es keine swap-baren Member hat. Oder arbeitet ihr dann einfach zuerst auf einer Kopie und weist die Member der Kopie dann einzeln zu, statt einen swap auszuführen? Ich meine, auch bei nicht swap-baren Inhalten kann eine Exception fliegen.

    Aaalso.
    Möglichkeit 1: Wenn ich ein simples struct mit nur wenigen Membern hab, also ein "bundle-of-data", lass ich den op= weg. Den Copy-Ctor auch. Ggf. kommt ein Kommentar hin damit offensichtlich ist, dass ich ihn nicht einfach vergessen hab.
    Möglichkeit 2: Ich hab eine Klasse, die einen Copy-Ctor hat. Meist lohnt sich dann ein swap und damit ist C&S ein Dreizeiler.
    Möglichkeit 3: Der Profiler sagt mir dass Copy&Swap zu komplex ist, dann kümmere ich mich persönlich drum.

    Alles zusammen ist es Schema-F, und das ist auch gut so - Schema-F gibt beim späteren Lesen keine Überraschungen.



  • Irgendwie ist der meiste Code mit boost immer potthässlich.



  • kryptiker schrieb:

    Irgendwie ist der meiste Code mit boost immer potthässlich.

    Das hat Code mit Templates und vielen Templateparametern allgemein an sich.



  • Werner Salomon schrieb:

    hustbaer schrieb:

    BTW: du könntest das ganze noch flexibler machen. Pack die "Transformation" (Dereferenzieren) in eine Traits-Klasse, und implementiere die "TransformingIterator" Klasse so, dass sie auch mit forward oder bidi Iteratoren funktioniert

    guter Hinweis, da hätte ich auch gleich drauf kommen können .. und wenn man jetzt noch weiß, dass boost sowas schon fix & fertig anbietet, wird's übersichtlich:

    Cool. Die Klasse kannte ich auch noch nicht. Hab aber schon halb damit gerechnet dass es sowas in der Boost gibt.


Anmelden zum Antworten