it-swarm.com.de

Wie werden benutzerdefinierte Iteratoren und const_iterators korrekt implementiert?

Ich habe eine benutzerdefinierte Containerklasse, für die ich die Klassen iterator und const_iterator Schreiben möchte.

Ich habe das noch nie zuvor gemacht und es gelang mir nicht, eine geeignete Anleitung zu finden. Was sind die Richtlinien für die Erstellung von Iteratoren und was muss ich beachten?

Ich möchte auch vermeiden, dass Code dupliziert wird (ich habe das Gefühl, dass const_iterator Und iterator viele Dinge gemeinsam haben; sollte einer den anderen unterordnen?).

Fußnote: Ich bin mir ziemlich sicher, dass Boost etwas hat, um dies zu erleichtern, aber ich kann es hier aus vielen dummen Gründen nicht verwenden.

211
ereOn
  • Wählen Sie den Iteratortyp, der zu Ihrem Container passt: Eingabe, Ausgabe, Weiterleitung usw.
  • Verwenden Sie Basisiteratorklassen aus der Standardbibliothek. Beispielsweise, std::iterator mit random_access_iterator_tag. Diese Basisklassen definieren alle Typdefinitionen, die von STL benötigt werden, und erledigen andere Arbeiten.
  • Um eine Codeduplizierung zu vermeiden, sollte die Iteratorklasse eine Schablonenklasse sein und durch "Werttyp", "Zeigertyp", "Referenztyp" oder alle (je nach Implementierung) parametrisiert werden. Zum Beispiel:

    // iterator class is parametrized by pointer type
    template <typename PointerType> class MyIterator {
        // iterator class definition goes here
    };
    
    typedef MyIterator<int*> iterator_type;
    typedef MyIterator<const int*> const_iterator_type;
    

    Beachten iterator_type und const_iterator_type Typdefinitionen: Dies sind Typen für Ihre Nicht-Konstanten- und Konstanten-Iteratoren.

Siehe auch: Standardbibliotheksreferenz

144
Andrey

Ich werde Ihnen zeigen, wie Sie einfach Iteratoren für Ihre benutzerdefinierten Container definieren können, aber nur für den Fall, dass ich eine c ++ 11-Bibliothek erstellt habe, mit der Sie einfach benutzerdefinierte Iteratoren mit benutzerdefiniertem Verhalten für jeden Typ von Container erstellen können, sei es zusammenhängend oder nicht zusammenhängend.

Sie finden es auf Github unter https://github.com/navyenzo/blIteratorAPI

Hier sind die einfachen Schritte zum Erstellen und Verwenden benutzerdefinierter Iteratoren:

  1. Erstellen Sie Ihre "benutzerdefinierte Iterator" -Klasse.
  2. Definieren Sie Typedefs in Ihrer Klasse "Benutzerdefinierter Container".
    • Zum Beispiel: typedef blRawIterator< Type > iterator;
    • Zum Beispiel: typedef blRawIterator< const Type > const_iterator;
  3. Definiere "begin" "end" Funktionen
    • Zum Beispiel: iterator begin(){return iterator(&m_data[0]);};
    • Zum Beispiel: const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Wir sind fertig!!!

Zum Schluss definieren wir unsere benutzerdefinierten Iteratorklassen:

HINWEIS:Bei der Definition benutzerdefinierter Iteratoren leiten wir uns von den Standard-Iteratorkategorien ab, damit STL-Algorithmen den von uns erstellten Iteratortyp kennen

In diesem Beispiel definiere ich einen Iterator mit wahlfreiem Zugriff und einen umgekehrten Iterator mit wahlfreiem Zugriff:

1.

//-------------------------------------------------------------------
// Raw iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawIterator : public std::iterator<std::random_access_iterator_tag,
                                           blDataType,
                                           ptrdiff_t,
                                           blDataType*,
                                           blDataType&>
{
public:

    blRawIterator(blDataType* ptr = nullptr){m_ptr = ptr;}
    blRawIterator(const blRawIterator<blDataType>& rawIterator) = default;
    ~blRawIterator(){}

    blRawIterator<blDataType>&                  operator=(const blRawIterator<blDataType>& rawIterator) = default;
    blRawIterator<blDataType>&                  operator=(blDataType* ptr){m_ptr = ptr;return (*this);}

    operator                                    bool()const
    {
        if(m_ptr)
            return true;
        else
            return false;
    }

    bool                                        operator==(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr == rawIterator.getConstPtr());}
    bool                                        operator!=(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr != rawIterator.getConstPtr());}

    blRawIterator<blDataType>&                  operator+=(const ptrdiff_t& movement){m_ptr += movement;return (*this);}
    blRawIterator<blDataType>&                  operator-=(const ptrdiff_t& movement){m_ptr -= movement;return (*this);}
    blRawIterator<blDataType>&                  operator++(){++m_ptr;return (*this);}
    blRawIterator<blDataType>&                  operator--(){--m_ptr;return (*this);}
    blRawIterator<blDataType>                   operator++(ptrdiff_t){auto temp(*this);++m_ptr;return temp;}
    blRawIterator<blDataType>                   operator--(ptrdiff_t){auto temp(*this);--m_ptr;return temp;}
    blRawIterator<blDataType>                   operator+(const ptrdiff_t& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
    blRawIterator<blDataType>                   operator-(const ptrdiff_t& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}

    ptrdiff_t                                   operator-(const blRawIterator<blDataType>& rawIterator){return std::distance(rawIterator.getPtr(),this->getPtr());}

    blDataType&                                 operator*(){return *m_ptr;}
    const blDataType&                           operator*()const{return *m_ptr;}
    blDataType*                                 operator->(){return m_ptr;}

    blDataType*                                 getPtr()const{return m_ptr;}
    const blDataType*                           getConstPtr()const{return m_ptr;}

protected:

    blDataType*                                 m_ptr;
};
//-------------------------------------------------------------------

2.

//-------------------------------------------------------------------
// Raw reverse iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawReverseIterator : public blRawIterator<blDataType>
{
public:

    blRawReverseIterator(blDataType* ptr = nullptr):blRawIterator<blDataType>(ptr){}
    blRawReverseIterator(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();}
    blRawReverseIterator(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
    ~blRawReverseIterator(){}

    blRawReverseIterator<blDataType>&           operator=(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
    blRawReverseIterator<blDataType>&           operator=(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();return (*this);}
    blRawReverseIterator<blDataType>&           operator=(blDataType* ptr){this->setPtr(ptr);return (*this);}

    blRawReverseIterator<blDataType>&           operator+=(const ptrdiff_t& movement){this->m_ptr -= movement;return (*this);}
    blRawReverseIterator<blDataType>&           operator-=(const ptrdiff_t& movement){this->m_ptr += movement;return (*this);}
    blRawReverseIterator<blDataType>&           operator++(){--this->m_ptr;return (*this);}
    blRawReverseIterator<blDataType>&           operator--(){++this->m_ptr;return (*this);}
    blRawReverseIterator<blDataType>            operator++(ptrdiff_t){auto temp(*this);--this->m_ptr;return temp;}
    blRawReverseIterator<blDataType>            operator--(ptrdiff_t){auto temp(*this);++this->m_ptr;return temp;}
    blRawReverseIterator<blDataType>            operator+(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr-=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
    blRawReverseIterator<blDataType>            operator-(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr+=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}

    ptrdiff_t                                   operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}

    blRawIterator<blDataType>                   base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
};
//-------------------------------------------------------------------

Jetzt irgendwo in Ihrer benutzerdefinierten Containerklasse:

template<typename blDataType>
class blCustomContainer
{
public: // The typedefs

    typedef blRawIterator<blDataType>              iterator;
    typedef blRawIterator<const blDataType>        const_iterator;

    typedef blRawReverseIterator<blDataType>       reverse_iterator;
    typedef blRawReverseIterator<const blDataType> const_reverse_iterator;

                            .
                            .
                            .

public:  // The begin/end functions

    iterator                                       begin(){return iterator(&m_data[0]);}
    iterator                                       end(){return iterator(&m_data[m_size]);}

    const_iterator                                 cbegin(){return const_iterator(&m_data[0]);}
    const_iterator                                 cend(){return const_iterator(&m_data[m_size]);}

    reverse_iterator                               rbegin(){return reverse_iterator(&m_data[m_size - 1]);}
    reverse_iterator                               rend(){return reverse_iterator(&m_data[-1]);}

    const_reverse_iterator                         crbegin(){return const_reverse_iterator(&m_data[m_size - 1]);}
    const_reverse_iterator                         crend(){return const_reverse_iterator(&m_data[-1]);}

                            .
                            .
                            .
    // This is the pointer to the
    // beginning of the data
    // This allows the container
    // to either "view" data owned
    // by other containers or to
    // own its own data
    // You would implement a "create"
    // method for owning the data
    // and a "wrap" method for viewing
    // data owned by other containers

    blDataType*                                    m_data;
};

VIEL GLÜCK!!!

48
Enzo

Boost hat etwas zu helfen: die Boost.Iterator-Bibliothek.

Genauer gesagt diese Seite: boost :: iterator_adaptor .

Sehr interessant ist das Tutorial-Beispiel , das eine vollständige Implementierung für einen benutzerdefinierten Typ von Grund auf zeigt.

template <class Value>
class node_iter
  : public boost::iterator_adaptor<
        node_iter<Value>                // Derived
      , Value*                          // Base
      , boost::use_default              // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
    >
{
 private:
    struct enabler {};  // a private type avoids misuse

 public:
    node_iter()
      : node_iter::iterator_adaptor_(0) {}

    explicit node_iter(Value* p)
      : node_iter::iterator_adaptor_(p) {}

    // iterator convertible to const_iterator, not vice-versa
    template <class OtherValue>
    node_iter(
        node_iter<OtherValue> const& other
      , typename boost::enable_if<
            boost::is_convertible<OtherValue*,Value*>
          , enabler
        >::type = enabler()
    )
      : node_iter::iterator_adaptor_(other.base()) {}

 private:
    friend class boost::iterator_core_access;
    void increment() { this->base_reference() = this->base()->next(); }
};

Der Hauptpunkt ist, wie bereits erwähnt, die Verwendung einer einzelnen Template-Implementierung und von typedef.

22
Matthieu M.

Sie vergessen oft, dass iterator in const_iterator Konvertieren muss, aber nicht umgekehrt. Hier ist eine Möglichkeit, dies zu tun:

template<class T, class Tag = void>
class IntrusiveSlistIterator
   : public std::iterator<std::forward_iterator_tag, T>
{
    typedef SlistNode<Tag> Node;
    Node* node_;

public:
    IntrusiveSlistIterator(Node* node);

    T& operator*() const;
    T* operator->() const;

    IntrusiveSlistIterator& operator++();
    IntrusiveSlistIterator operator++(int);

    friend bool operator==(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
    friend bool operator!=(IntrusiveSlistIterator a, IntrusiveSlistIterator b);

    // one way conversion: iterator -> const_iterator
    operator IntrusiveSlistIterator<T const, Tag>() const;
};

In der obigen Notiz wird IntrusiveSlistIterator<T> In IntrusiveSlistIterator<T const> Konvertiert. Wenn T bereits const ist, wird diese Konvertierung niemals verwendet.

21

Ich weiß nicht, ob Boost irgendetwas hat, das helfen könnte.

Mein bevorzugtes Muster ist einfach: Nehmen Sie ein Template-Argument, das gleich value_type Ist, entweder const-qualifiziert oder nicht. Bei Bedarf auch ein Knotentyp. Dann passt alles zusammen.

Denken Sie daran, alles zu parametrisieren (template-ize), einschließlich des Kopierkonstruktors und operator==. Die Semantik von const führt größtenteils zu einem korrekten Verhalten.

template< class ValueType, class NodeType >
struct my_iterator
 : std::iterator< std::bidirectional_iterator_tag, T > {
    ValueType &operator*() { return cur->payload; }

    template< class VT2, class NT2 >
    friend bool operator==
        ( my_iterator const &lhs, my_iterator< VT2, NT2 > const &rhs );

    // etc.

private:
    NodeType *cur;

    friend class my_container;
    my_iterator( NodeType * ); // private constructor for begin, end
};

typedef my_iterator< T, my_node< T > > iterator;
typedef my_iterator< T const, my_node< T > const > const_iterator;
16
Potatoswatter

Es gibt viele gute Antworten, aber ich habe einen Template-Header erstellt. Ich verwende diesen, der ziemlich übersichtlich und einfach zu bedienen ist.

Um Ihrer Klasse einen Iterator hinzuzufügen, müssen Sie lediglich eine kleine Klasse schreiben, die den Status des Iterators mit 7 kleinen Funktionen darstellt, von denen 2 optional sind:

#include <iostream>
#include <vector>
#include "iterator_tpl.h"

struct myClass {
  std::vector<float> vec;

  // Add some sane typedefs for STL compliance:
  STL_TYPEDEFS(float);

  struct it_state {
    int pos;
    inline void begin(const myClass* ref) { pos = 0; }
    inline void next(const myClass* ref) { ++pos; }
    inline void end(const myClass* ref) { pos = ref->vec.size(); }
    inline float& get(myClass* ref) { return ref->vec[pos]; }
    inline bool cmp(const it_state& s) const { return pos != s.pos; }

    // Optional to allow operator--() and reverse iterators:
    inline void prev(const myClass* ref) { --pos; }
    // Optional to allow `const_iterator`:
    inline const float& get(const myClass* ref) const { return ref->vec[pos]; }
  };
  // Declare typedef ... iterator;, begin() and end() functions:
  SETUP_ITERATORS(myClass, float&, it_state);
  // Declare typedef ... reverse_iterator;, rbegin() and rend() functions:
  SETUP_REVERSE_ITERATORS(myClass, float&, it_state);
};

Dann können Sie es verwenden, wie Sie es von einem STL-Iterator erwarten würden:

int main() {
  myClass c1;
  c1.vec.Push_back(1.0);
  c1.vec.Push_back(2.0);
  c1.vec.Push_back(3.0);

  std::cout << "iterator:" << std::endl;
  for (float& val : c1) {
    std::cout << val << " "; // 1.0 2.0 3.0
  }

  std::cout << "reverse iterator:" << std::endl;
  for (auto it = c1.rbegin(); it != c1.rend(); ++it) {
    std::cout << *it << " "; // 3.0 2.0 1.0
  }
}

Ich hoffe, es hilft.

8
VinGarcia