Bagaimana cara menerapkan iterator dan const_iterators kustom?

240

Saya memiliki kelas wadah kustom yang ingin saya tulis iteratordan const_iteratorkelasnya.

Saya tidak pernah melakukan ini sebelumnya dan saya gagal menemukan cara yang tepat. Apa pedoman tentang pembuatan iterator, dan apa yang harus saya ketahui?

Saya juga ingin menghindari duplikasi kode (saya merasakannya const_iteratordan iteratorberbagi banyak hal; haruskah satu subklas yang lain?).

Catatan kaki: Saya yakin Boost memiliki sesuatu untuk meringankan ini tetapi saya tidak dapat menggunakannya di sini, karena banyak alasan bodoh.

sebelum
sumber
Apakah Pola Iterator GoF dipertimbangkan?
DumbCoder
3
@ DumbCoder: Dalam C ++ sering diinginkan untuk memiliki iterator yang sesuai dengan STL, karena mereka akan bekerja dengan baik dengan semua wadah dan algoritma yang ada yang disediakan oleh STL. Meskipun konsepnya serupa, ada beberapa perbedaan dengan pola yang diusulkan oleh GoF.
Björn Pollex
Saya telah memposting sampel iterator khusus di sini
Valdemar_Rudolfovich
1
Kompleksitas jawaban ini menunjukkan bahwa C ++ adalah bahasa yang tidak layak untuk apa pun selain tugas pekerjaan rumah untuk undergrad up-jumped, atau jawabannya terlalu rumit dan salah. Harus ada cara yang lebih mudah dalam Cpp? Seperti CMake dan Automake sebelum relatif dibuat, C mentah yang direbus dari prototipe python tampaknya jauh lebih mudah daripada ini.
Christopher

Jawaban:

157
  • Pilih jenis iterator yang sesuai dengan wadah Anda: input, output, maju dll.
  • Gunakan kelas iterator dasar dari perpustakaan standar. Sebagai contoh, std::iteratordengan random_access_iterator_tag. Kelas dasar ini mendefinisikan semua definisi tipe yang diperlukan oleh STL dan melakukan pekerjaan lain.
  • Untuk menghindari duplikasi kode, kelas iterator harus berupa kelas templat dan ditentukan oleh "tipe nilai", "tipe pointer", "tipe referensi" atau semuanya (tergantung pada implementasi). Sebagai contoh:

    // 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;

    Perhatikan iterator_typedan const_iterator_typeketik definisi: mereka adalah tipe untuk non-const dan constator Anda.

Lihat Juga: referensi perpustakaan standar

EDIT: std::iterator sudah tidak digunakan lagi sejak C ++ 17. Lihat diskusi terkait di sini .

Andrey
sumber
8
@Potatoswatter: Belum downvoted ini, tapi, hei, random_access_iteratortidak dalam standar dan jawabannya tidak menangani konversi yang bisa berubah ke const. Anda mungkin ingin mewarisi, misalnya std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>.
Yakov Galka
2
Ya, saya tidak begitu yakin bagaimana ini bekerja. Jika saya memiliki metode ini RefType operator*() { ... }, saya selangkah lebih dekat - tetapi itu tidak membantu, karena saya masih membutuhkannya RefType operator*() const { ... }.
Autumnsault
31
std::iteratoradalah diusulkan untuk bantahan di C ++ 17 .
TypeIA
20
std::iterator telah ditinggalkan
diapir
5
Jika ini sudah ditinggalkan, apa cara yang tepat "baru" untuk melakukannya?
SasQ
56

Saya akan menunjukkan kepada Anda bagaimana Anda dapat dengan mudah mendefinisikan iterator untuk wadah khusus Anda, tetapi untuk berjaga-jaga jika saya telah membuat pustaka c ++ 11 yang memungkinkan Anda untuk membuat iterator khusus dengan perilaku khusus untuk semua jenis wadah, berdekatan atau tidak berdampingan.

Anda dapat menemukannya di Github

Berikut adalah langkah-langkah sederhana untuk membuat dan menggunakan iterator khusus:

  1. Buat kelas "custom iterator" Anda.
  2. Tentukan typedef di kelas "wadah khusus" Anda.
    • misalnya typedef blRawIterator< Type > iterator;
    • misalnya typedef blRawIterator< const Type > const_iterator;
  3. Tentukan fungsi "mulai" dan "akhiri"
    • misalnya iterator begin(){return iterator(&m_data[0]);};
    • misalnya const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Dilakukan!!!

Akhirnya, ke mendefinisikan kelas iterator khusus kami:

CATATAN: Saat mendefinisikan iterator khusus, kami berasal dari kategori iterator standar untuk memberi tahu algoritme STL tentang jenis iterator yang kami buat.

Dalam contoh ini, saya mendefinisikan iterator akses acak dan iterator akses acak terbalik:

  1. //-------------------------------------------------------------------
    // Raw iterator with random access
    //-------------------------------------------------------------------
    template<typename blDataType>
    class blRawIterator
    {
    public:
    
        using iterator_category = std::random_access_iterator_tag;
        using value_type = blDataType;
        using difference_type = std::ptrdiff_t;
        using pointer = blDataType*;
        using reference = 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 difference_type& movement){m_ptr += movement;return (*this);}
        blRawIterator<blDataType>&                  operator-=(const difference_type& movement){m_ptr -= movement;return (*this);}
        blRawIterator<blDataType>&                  operator++(){++m_ptr;return (*this);}
        blRawIterator<blDataType>&                  operator--(){--m_ptr;return (*this);}
        blRawIterator<blDataType>                   operator++(int){auto temp(*this);++m_ptr;return temp;}
        blRawIterator<blDataType>                   operator--(int){auto temp(*this);--m_ptr;return temp;}
        blRawIterator<blDataType>                   operator+(const difference_type& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
        blRawIterator<blDataType>                   operator-(const difference_type& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
    
        difference_type                             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 difference_type& movement){this->m_ptr -= movement;return (*this);}
        blRawReverseIterator<blDataType>&           operator-=(const difference_type& 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++(int){auto temp(*this);--this->m_ptr;return temp;}
        blRawReverseIterator<blDataType>            operator--(int){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;}
    
        difference_type                             operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}
    
        blRawIterator<blDataType>                   base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
    };
    //-------------------------------------------------------------------

Sekarang di suatu tempat di kelas wadah khusus Anda:

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;
};
Enzo
sumber
Saya pikir operator + dan operator- mungkin memiliki operasi mundur. Sepertinya operator + mengurangi gerakan dari pointer yang tidak menambahkan dan operator- menambahkannya. Ini tampaknya mundur
Beached
Ini untuk iterator terbalik, operator + harus mundur dan operator- harus maju
Enzo
2
Luar biasa. Jawaban yang diterima terlalu tinggi. Ini luar biasa. Enzo terima kasih.
FernandoZ
Anda perlu mengedit jawaban Anda. Dengan asumsi m_data dialokasikan dengan elemen m_size Anda mendapatkan Perilaku Tidak Terdefinisi: m_data[m_size]adalah UB. Anda bisa memperbaikinya dengan menggantinya dengan m_data+m_size. Untuk iterator terbalik, keduanya m_data[-1]dan m_data-1tidak benar (UB). Untuk memperbaiki reverse_iterators Anda perlu menggunakan "pointer ke trik elemen berikutnya".
Arnaud
Arnaud, saya baru saja menambahkan anggota pointer ke kelas wadah kustom yang lebih baik menunjukkan apa yang saya maksud.
Enzo
24

Mereka sering lupa yang iteratorharus masuk const_iteratortetapi tidak sebaliknya. Ini cara untuk melakukannya:

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;
};

Dalam pemberitahuan di atas cara IntrusiveSlistIterator<T>konversi ke IntrusiveSlistIterator<T const>. Jika Tsudah constkonversi ini tidak pernah digunakan.

Maxim Egorushkin
sumber
Sebenarnya, Anda juga bisa melakukannya sebaliknya dengan mendefinisikan copy constructor yang merupakan template, itu tidak akan dikompilasi jika Anda mencoba untuk melemparkan tipe yang mendasarinya dari constke non- const.
Matthieu M.
Tidakkah Anda akan berakhir dengan yang tidak valid IntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() const?
Potatoswatter
Ah, ini benar, tetapi Comeau memberi peringatan dan saya curiga banyak yang lain juga akan melakukannya. Sebuah enable_ifmungkin memperbaikinya, tapi ...
Potatoswatter
Saya tidak peduli dengan enable_if karena kompiler menonaktifkannya, walaupun beberapa kompiler memberikan peringatan (g ++ menjadi anak yang baik tidak memperingatkan).
Maxim Egorushkin
1
@ Matthieu: Jika seseorang pergi dengan konstruktor template, ketika mengkonversi const_iterator ke iterator kompiler menghasilkan kesalahan di dalam konstruktor, membuat pengguna menggaruk kepalanya dalam kebingungan dan mengucapkan wtf. Dengan operator konversi yang saya posting, kompiler hanya mengatakan bahwa tidak ada konversi yang sesuai dari const_iterator ke iterator, yang, IMO, lebih jelas.
Maxim Egorushkin
23

Boost memiliki sesuatu untuk membantu: perpustakaan Boost.Iterator.

Lebih tepatnya halaman ini: boost :: iterator_adaptor .

Yang sangat menarik adalah Contoh Tutorial yang memperlihatkan implementasi lengkap, dari awal, untuk jenis kustom.

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(); }
};

Poin utama, seperti yang telah dikutip, adalah menggunakan implementasi template tunggal dan typedefitu.

Matthieu M.
sumber
Bisakah Anda menjelaskan arti dari komentar ini? // a private type avoids misuse
kevinarpe
@ kevinarpe: enablertidak pernah dimaksudkan untuk menjadi penyedia oleh penelepon, jadi dugaan saya adalah mereka menjadikannya pribadi untuk menghindari orang yang secara tidak sengaja berusaha melewatinya. Saya tidak berpikir, begitu saja, bahwa itu dapat menciptakan masalah untuk benar-benar melewatinya, karena perlindungan ada di dalamnya enable_if.
Matthieu M.
16

Saya tidak tahu apakah Boost memiliki sesuatu yang akan membantu.

Pola pilihan saya sederhana: ambil argumen templat yang sama dengan value_type, baik konst kualifikasi atau tidak. Jika perlu, juga jenis simpul. Lalu, yah, semua jenis jatuh ke tempatnya.

Hanya ingat untuk parameterize (template-ize) semua yang perlu, termasuk copy constructor dan operator==. Untuk sebagian besar, semantik constakan menciptakan perilaku yang benar.

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;
Potatoswatter
sumber
Catatan: sepertinya konversi Anda iterator-> const_iterator dan kembali rusak.
Maxim Egorushkin
@ Maxim: Ya, saya benar-benar tidak dapat menemukan contoh menggunakan teknik saya: vP. Saya tidak yakin maksud Anda konversi terputus, karena saya tidak mengilustrasikannya, tetapi mungkin ada masalah mengakses curdari iterator yang berlawanan. Solusi yang muncul di benak saya adalah friend my_container::const_iterator; friend my_container::iterator;, tapi saya tidak berpikir itulah yang saya lakukan sebelumnya ... pokoknya garis besar umum ini berfungsi.
Potatoswatter
1
* buat itu friend classdalam kedua kasus.
Potatoswatter
Sudah lama, tapi saya ingat sekarang bahwa konversi harus didasarkan (oleh SFINAE) pada formasi yang baik dari inisialisasi anggota yang mendasarinya. Ini mengikuti pola SCARY (tetapi posting ini ada sebelum terminologi itu).
Potatoswatter
13

Ada banyak jawaban bagus tapi saya membuat tajuk templat yang saya gunakan yang cukup ringkas dan mudah digunakan.

Untuk menambahkan iterator ke kelas Anda, hanya perlu menulis kelas kecil untuk mewakili keadaan iterator dengan 7 fungsi kecil, yang 2 di antaranya opsional:

#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);
};

Kemudian Anda dapat menggunakannya seperti yang Anda harapkan dari iterator STL:

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
  }
}

Saya harap ini membantu.

VinGarcia
sumber
1
File template ini menyelesaikan semua masalah iterator saya!
Perrykipkerrie