Özel yineleyiciler ve const_iterators nasıl doğru şekilde uygulanır?


240

Ben yazmak istiyorum özel bir konteyner sınıfı var iteratorveconst_iterator sınıfları .

Bunu daha önce hiç yapmadım ve uygun bir nasıl yapıldığını bulamadım. Yineleyici oluşturma ile ilgili yönergeler nelerdir ve nelere dikkat etmeliyim?

Ayrıca kod çoğaltma önlemek istiyorum (bunu hissediyorum const_iteratorveiterator birçok şey paylaşmak; biri diğerine alt sınıf mı?).

Dipnot: Eminim Boost'un bunu kolaylaştıracak bir şeyleri vardır, ancak burada birçok aptal nedenden dolayı kullanamam.



GoF Yineleyici Modeli hiç dikkate alınmıyor mu?
DumbCoder

3
@DumbCoder: C ++ 'da STL uyumlu yineleyicilere sahip olmak genellikle arzu edilir, çünkü STL tarafından sağlanan tüm mevcut kapsayıcılar ve algoritmalarla güzel bir şekilde çalışırlar. Kavram benzer olsa da, GoF tarafından önerilen modelde bazı farklılıklar vardır.
Björn Pollex


1
Bu cevapların karmaşıklığı, C ++ 'ın yukarı sıçrayan lisans öğrencileri için ev ödevlerinden başka hiçbir şeye layık olmayan bir dil olduğunu ya da cevapların karmaşık ve yanlış olduğunu göstermektedir. Cpp daha kolay bir yolu olmalı? CMake ve Automake gibi, göreceli olarak daha önce olduğu gibi, bir python prototipinden haşlanmış ham C bundan daha kolay görünüyor.
Christopher

Yanıtlar:


157
  • Konteynerinize uyan yineleyici türünü seçin: giriş, çıkış, ileri vb.
  • Standart kütüphaneden base iterator sınıflarını kullanın. Örneğin, bu temel sınıflar std::iteratorile random_access_iterator_tagSTL'nin gerektirdiği tüm tür tanımlarını tanımlar ve başka işler yapar.
  • Kod yinelemesinden kaçınmak için yineleyici sınıfı bir şablon sınıfı olmalı ve "değer türü", "işaretçi türü", "referans türü" veya hepsine göre parametrelendirilmelidir (uygulamaya bağlıdır). Örneğin:

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

    Bildirim iterator_typeve const_iterator_typetür tanımları: const olmayan ve const yineleyicileriniz için türlerdir.

Ayrıca Bakınız: standart kütüphane referansı

EDIT: std::iterator C ++ 17'den beri kullanımdan kaldırıldı. İlgili bir tartışmaya buradan bakın .


8
@Potatoswatter: Bunu düşürmediniz, ama hey, random_access_iteratorstandartta değil ve cevap değiş tokuş dönüşümü ile başa çıkmıyor. Muhtemelen miras almak istiyorsunuz std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>.
Yakov Galka

2
Evet, bunun nasıl işlediğinden tam olarak emin değilim. Metodum varsa RefType operator*() { ... }, bir adım daha yakınım - ama yardımcı olmuyor, çünkü hala ihtiyacım var RefType operator*() const { ... }.
Autumnsault



5
Bu onaylanmadıysa, bunun yerine uygun "yeni" yol nedir?
SasQ

56

Size özel kapsayıcılarınız için yineleyicileri kolayca nasıl tanımlayabileceğinizi göstereceğim, ancak sadece herhangi bir kapsayıcı türü için bitişik veya bitişik olmayan.

Github'da bulabilirsiniz

Özel yineleyiciler oluşturmak ve kullanmak için basit adımlar şunlardır:

  1. "Özel yineleyici" sınıfınızı oluşturun.
  2. "Özel kapsayıcı" sınıfınızda typedefs tanımlayın.
    • Örneğin typedef blRawIterator< Type > iterator;
    • Örneğin typedef blRawIterator< const Type > const_iterator;
  3. "Başlangıç" ve "bitiş" işlevlerini tanımlama
    • Örneğin iterator begin(){return iterator(&m_data[0]);};
    • Örneğin const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Yapılmıştı!!!

Son olarak, özel yineleyici sınıflarımızı tanımlamak üzerine:

NOT: Özel yineleyiciler tanımlarken, STL algoritmalarının yaptığımız yineleyicinin türünü bilmesini sağlamak için standart yineleyici kategorilerinden türetilir.

Bu örnekte, rasgele erişim yineleyicisi ve ters rasgele erişim yineleyicisi tanımladım:

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

Şimdi özel kapsayıcı sınıfınızda bir yerde:

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

Bence operatör + ve operatör- işlemleri geriye doğru taşıyabilir. Operatör +, işaretçiyi eklemeyen işaretçiden çıkarıyor ve operatör ekliyor gibi görünüyor. Bu geriye görünüyor
beached

Tersine yineleyici içindir, operatör + geri gitmeli ve operatör
Enzo

2
Muhteşem. Kabul edilen cevap çok yüksek. Bu harika. Teşekkürler Enzo.
FernandoZ

Cevabınızı düzenlemeniz gerekiyor. M_data'nın m_size öğeleriyle ayrıldığı varsayılarak, Tanımsız Davranış alırsınız: m_data[m_size]UB. Değiştirerek basitçe düzeltebilirsiniz m_data+m_size. Ters tekrarlayıcılara için, her ikisi de m_data[-1]ve m_data-1yanlış (UB) vardır. Reverse_iterators düzeltmek için "sonraki öğe hile işaretçiler" kullanmanız gerekir.
Arnaud

Arnaud, işaretçi üyeyi özel konteyner sınıfına eklemiştim, ne demek istediğimi daha iyi gösteriyor.
Enzo

24

Genellikle bunun iteratordönüşmesi gerektiğini unuturlar, const_iteratorancak tam tersi değil. İşte bunu yapmanın bir yolu:

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

Yukarıdaki bildirime nasıl IntrusiveSlistIterator<T>dönüşür IntrusiveSlistIterator<T const>. Eğer Tzaten constasla kullanılan alır bu dönüşüm.


Aslında, şablon olan bir kopya oluşturucu tanımlayarak bunu başka bir şekilde de yapabilirsiniz, temel türü türünü constolmayan öğeye dönüştürmeye çalışırsanız derlenmez const.
Matthieu M.

Sonunda geçersiz olmayacak mısın IntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() const?
Potatoswatter

Ah, bu geçerli, ama Comeau bir uyarı veriyor ve birçoğunun da yapacağından şüpheleniyorum. Bir enable_if... bunu düzeltmek ama olabilir
Potatoswatter

Bazı derleyiciler bir uyarı vermek rağmen (g ++ iyi bir çocuk olmak uyarmaz) derleyici zaten devre dışı, çünkü enable_if ile uğraşmadı.
Maxim Egorushkin

1
@Matthieu: Bir şablon oluşturucu ile giderse, const_iterator'ı yineleyiciye dönüştürürken derleyici yapıcı içinde bir hata üretir ve kullanıcının kafasını kafa karışıklığı ve wtf ile çizmesine neden olur. Yayınladığım dönüşüm operatörü ile derleyici sadece const_iterator'dan iteratöre, IMO'nun daha açık olduğu uygun bir dönüşüm olmadığını söylüyor.
Maxim Egorushkin

23

Boost'un yardım etmesi gereken bir şey var: Boost.Iterator kütüphanesi.

Daha doğrusu bu sayfa: boost :: iterator_adaptor .

Çok ilginç olan, özel bir tür için sıfırdan eksiksiz bir uygulamayı gösteren Eğitim Örneği'dir.

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

Ana nokta, daha önce belirtildiği gibi, tek bir şablon uygulaması ve typedefonu kullanmaktır.


Bu yorumun anlamını açıklayabilir misiniz? // a private type avoids misuse
kevinarpe

@kevinarpe: enablerasla arayan tarafından sağlayıcı olmayı amaçlamaz, bu yüzden tahminim insanların yanlışlıkla geçmeye çalışmasını önlemek için özel yapmalarıdır. Elimden geldiği gibi, koruma olduğu için onu geçmenin herhangi bir sorun yaratabileceğini düşünmüyorum enable_if.
Matthieu M.7

16

Boost'un yardımcı olacak bir şeyi olup olmadığını bilmiyorum.

Tercih ettiğim kalıp basit: value_typeconst nitelikli ya da eşit olmayan bir şablon argümanı al . Gerekirse, bir düğüm tipi de. Sonra, her şey yerine düşer.

Kopya oluşturucu ve dahil olmak üzere olması gereken her şeyi parametreleştirmeyi (template -ize) unutmayın operator==. Çoğunlukla, anlambilimi constdoğru davranış yaratacaktır.

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;

Not: dönüşümleriniz yineleyici-> const_iterator ve geri kırık gibi görünüyor.
Maxim Egorushkin

@Maxim: Evet, tekniğimi kullanmanın herhangi bir örneğini bulamıyorum: vP. Dönüşümleri curbozduğunuzu kastettiğinizden emin değilim, çünkü onları açıklamamıştım, ancak karşıt sabitliğin yineleyicisinden erişimde bir sorun olabilir . Akla gelen çözüm şu friend my_container::const_iterator; friend my_container::iterator;, ama daha önce böyle yaptığımı sanmıyorum… zaten bu genel taslak işe yarıyor.
Potatoswatter

1
* bunu friend classher iki durumda da yapın.
Potatoswatter

Biraz zaman oldu, ama şimdi hatırlıyorum dönüşümler (SFINAE tarafından) altta yatan üye başlatmanın iyi biçimliliğine dayanmalıdır. Bu, KORKUNÇ kalıbı takip eder (ancak bu yazı bu terminolojiden önce gelir).
Potatoswatter

13

Çok sayıda iyi cevap var ama kullandığım oldukça kısa ve kullanımı kolay bir şablon başlığı oluşturdum .

Sınıfınıza bir yineleyici eklemek için, yineleyicinin durumunu temsil eden küçük bir sınıf yazmanız gerekir, bunlardan 2 tanesi isteğe bağlıdır; bunlardan 2 tanesi isteğe bağlıdır:

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

Sonra bir STL yineleyicisinden beklediğiniz gibi kullanabilirsiniz:

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

Umut ediyorum bu yardım eder.


1
Bu şablon dosyası tüm yineleyici sorunlarımı çözdü!
Perrykipkerrie
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.