사용자 정의 반복자와 const_iterator를 올바르게 구현하는 방법은 무엇입니까?


240

iteratorand const_iterator클래스 를 작성하려는 사용자 정의 컨테이너 클래스가 있습니다.

나는 전에 이것을 한 적이 없으며 적절한 방법을 찾지 못했습니다. 반복자 생성에 관한 지침은 무엇이며 무엇을 알고 있어야합니까?

또한 피하기 코드 중복을 같은 (나는 것을 느낄 것 const_iterator하고 iterator, 하나가 다른 서브 클래스해야 공유 많은 것들?).

각주 : Boost는 이것을 쉽게 할 수있는 것이 확실하지만 많은 어리석은 이유로 여기에서는 사용할 수 없습니다.



GoF 반복자 패턴이 전혀 고려되지 않습니까?
DumbCoder

3
@DumbCoder : C ++에서는 STL이 제공하는 모든 기존 컨테이너 및 알고리즘과 잘 작동하므로 STL 호환 반복기를 갖는 것이 바람직합니다. 개념은 비슷하지만 GoF에서 제안한 패턴과 약간의 차이가 있습니다.
Björn Pollex

나는 사용자 정의 반복자의 샘플을 게시 한 여기
Valdemar_Rudolfovich

1
이러한 답변의 복잡성으로 인해 C ++은 학점을 많이받는 학부생을위한 숙제 이외의 언어는 가치가 없거나, 답변이 지나치게 복잡하고 잘못되었음을 나타냅니다. Cpp에는 더 쉬운 방법이 있어야합니까? CMake 및 Automake와 비교하기 전에 Python 프로토 타입에서 비롯된 원시 C는 이것보다 훨씬 쉽습니다.
크리스토퍼

답변:


157
  • 컨테이너에 맞는 반복기 유형 (입력, 출력, 전달 등)을 선택하십시오.
  • 표준 라이브러리에서 기본 반복기 클래스를 사용하십시오. 예를 들어, std::iterator함께 random_access_iterator_tag.These 기본 클래스는 STL에서 요구하는 모든 유형 정의를 정의하고 다른 작업을한다.
  • 코드 중복을 피하기 위해 반복자 클래스는 템플릿 클래스 여야하며 "값 유형", "포인터 유형", "참조 유형"또는 이들 모두에 의해 매개 변수화되어야합니다 (구현에 따라 다름). 예를 들면 다음과 같습니다.

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

    주의 사항 iterator_typeconst_iterator_type유형 정의 : 비 const 및 const 반복자에 대한 유형입니다.

참조 : 표준 라이브러리 참조

편집 : std::iterator C ++ 17부터 더 이상 사용되지 않습니다. 여기 에서 관련 토론을 참조 하십시오 .


8
@ Potatoswatter : 이것을 하향 조정 random_access_iterator하지는 않았지만 표준이 아니며 대답은 가변에서 const 로의 변환을 처리하지 않습니다. 예를 들어 상속받을 수도 있습니다 std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>.
Yakov Galka

2
예, 어떻게 작동하는지 잘 모르겠습니다. 내가 방법을 가지고 있다면 RefType operator*() { ... }, 한 걸음 더 가까이에 있습니다. 그러나 여전히 도움이되지 않습니다 RefType operator*() const { ... }.
Autumnsault

31
std::iterator되는 C ++ 17에서 중단 제안 .
TypeIA


5
이것이 더 이상 사용되지 않는 경우 대신 올바른 "새로운"방법은 무엇입니까?
SasQ

56

커스텀 컨테이너에 대한 반복자를 쉽게 정의하는 방법을 보여 주려고하지만, 컨테이너 유형에 상관없이 커스텀 반복자를 사용하여 커스텀 반복기를 쉽게 만들 수있는 c ++ 11 라이브러리를 만든 경우 인접하지 않습니다.

Github에서 찾을 수 있습니다

다음은 사용자 정의 반복자를 작성하고 사용하는 간단한 단계입니다.

  1. "custom iterator"클래스를 작성하십시오.
  2. "custom container"클래스에서 typedef를 정의하십시오.
    • 예 : typedef blRawIterator< Type > iterator;
    • 예 : typedef blRawIterator< const Type > const_iterator;
  3. "시작"및 "종료"기능 정의
    • 예 : iterator begin(){return iterator(&m_data[0]);};
    • 예 : const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. 우리는 끝났어 !!!

마지막으로, 커스텀 반복자 클래스를 정의 할 때 :

참고 : 사용자 정의 반복자를 정의 할 때 표준 반복자 범주에서 파생되어 STL 알고리즘이 만든 반복자의 유형을 알립니다.

이 예에서는 랜덤 액세스 반복자와 역방향 랜덤 액세스 반복자를 정의합니다.

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

이제 커스텀 컨테이너 클래스의 어딘가에 :

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

나는 operator +와 operator-가 작업을 거꾸로 할 수 있다고 생각합니다. operator +는 포인터를 추가하지 않고 operator-를 추가하여 이동을 뺀 것처럼 보입니다. 이 거꾸로 보인다
올린

역 이터레이터를위한 것이고, operator +는 뒤로 가고, operator-는 앞으로 나아가 야합니다
Enzo

2
대박. 허용 된 답변이 너무 높습니다. 대단해. Enzo 감사합니다.
FernandoZ

답을 편집해야합니다. m_data가 m_size 요소와 함께 할당되었다고 가정하면 Undefined Behavior : m_data[m_size]is UB입니다. 로 바꾸면 간단히 고칠 수 있습니다 m_data+m_size. 역 반복자를 들어, 모두 m_data[-1]와는 m_data-1잘못된 (UB)입니다. reverse_iterator를 수정하려면 "다음 요소 트릭에 대한 포인터"를 사용해야합니다.
Arnaud

Arnaud, 방금 포인터 멤버를 사용자 정의 컨테이너 클래스에 추가하여 내가 의미하는 바를 더 잘 보여줍니다.
Enzo

24

그들은 종종 다른 방식으로 iterator전환해서는 안된다는 것을 잊어 버립니다 const_iterator. 이를 수행하는 방법은 다음과 같습니다.

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

위의 통지에서 IntrusiveSlistIterator<T>로 변환 하는 방법 IntrusiveSlistIterator<T const>. 경우는 T이미 const결코 사용되지 도착이 변환.


실제로 템플릿 인 복사 생성자를 정의하여 다른 방법으로 할 수도 있습니다. 기본 유형을에서 const로 변환 하려고 시도하면 컴파일되지 않습니다 const.
Matthieu M.

당신은 잘못으로 끝나지 IntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() const않습니까?
Potatoswatter

아, 그것은 유효하지만 Comeau는 경고를하고 다른 많은 사람들도 그럴 것이라고 생각합니다. 은 enable_if... 그것을 해결하지만, 수
Potatoswatter

컴파일러는 어쨌든 그것을 비활성화하기 때문에 enable_if를 귀찮게하지 않았지만 일부 컴파일러는 경고를하지만 (g ++은 좋은 소년이 경고하지 않습니다).
Maxim Egorushkin

1
@ Matthieu : 템플릿 생성자를 사용하는 경우 const_iterator를 반복자로 변환 할 때 컴파일러는 생성자 내부에서 오류를 생성하여 사용자가 혼란스럽게 머리를 긁습니다. 내가 게시 한 변환 연산자를 사용하면 컴파일러는 const_iterator에서 iterator 로의 적절한 변환이 없다고 말하며 IMO는 더 명확합니다.
Maxim Egorushkin

23

Boost에는 도움이 될만한 것이 있습니다 : Boost.Iterator 라이브러리.

보다 정확하게이 페이지는 boost :: iterator_adaptor 입니다.

흥미로운 점은 사용자 정의 유형에 대한 완전한 구현을 처음부터 보여주는 Tutorial Example 입니다.

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

이미 언급했듯이 요점은 단일 템플릿 구현과 typedef이를 사용하는 것입니다.


이 의견의 의미를 설명해 주시겠습니까? // a private type avoids misuse
kevinarpe

@ kevinarpe : enabler발신자가 제공자를 제공하도록 의도 된 것은 아니므로 사람들이 실수로 그것을 전달하려고 시도하지 않도록 비공개로 만들 것입니다. 보호 기능이에 있기 때문에 실제로 그것을 통과하는 데 문제가 발생할 수 있다고 생각하지 않습니다 enable_if.
Matthieu M.

16

Boost에 도움이 될만한 것이 있는지 모르겠습니다.

내가 선호하는 패턴은 간단합니다. value_typeconst 한정 여부에 관계없이 같은 템플릿 인수를 사용하십시오 . 필요한 경우 노드 유형도 있습니다. 그러면 모든 것이 제자리에 들어갑니다.

복사 생성자 및를 포함하여 필요한 모든 것을 매개 변수화 (템플릿 화)해야합니다 operator==. 대부분의 의미는 const올바른 동작을 만듭니다.

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;

참고 : 전환 반복자-> const_iterator 및 뒷면이 깨진 것처럼 보입니다.
Maxim Egorushkin

@Maxim : 예, 실제로 내 기술을 사용하는 예를 찾을 수 없습니다 : vP. 나는 단순히 변환을 설명하지 않았기 때문에 변환이 중단 cur되었다는 것이 무엇인지 확실하지 않지만 반대 편의 반복자에서 액세스하는 데 문제가있을 수 있습니다 . 생각 나게하는 해결책은입니다 만 friend my_container::const_iterator; friend my_container::iterator;, 이전에는 그렇게했다고 생각하지 않습니다 ... 어쨌든이 일반적인 개요가 작동합니다.
Potatoswatter

1
* friend class두 경우 모두 확인하십시오 .
Potatoswatter

어느 정도 시간이 지났지 만 이제는 기본 멤버 초기화의 올바른 형식에 대한 변환이 (SFINAE에 의해) 제시되어야 함을 기억합니다. 이것은 SCARY 패턴을 따릅니다 (그러나이 게시물은 그 용어보다 우선합니다).
Potatoswatter

13

좋은 답변이 많이 있지만 매우 간결하고 사용하기 쉬운 템플릿 헤더를 만들었습니다 .

클래스에 반복자를 추가하려면 7 개의 작은 함수로 반복자의 상태를 나타 내기 위해 작은 클래스 만 작성하면됩니다. 그 중 2 개는 선택 사항입니다.

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

그런 다음 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
  }
}

도움이 되길 바랍니다.


1
이 템플릿 파일은 모든 반복자 문제를 해결했습니다!
Perrykipkerrie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.