カスタムイテレータとconst_iteratorsを正しく実装するにはどうすればよいですか?


240

iteratorおよびconst_iteratorクラスを記述したいカスタムコンテナクラスがあります。

私はこれまでこれをやったことがなく、適切なハウツーを見つけることができませんでした。イテレータの作成に関するガイドラインは何ですか。また、何に注意する必要がありますか?

また、コードの重複を避けたいと思います(それを感じてconst_iteratoriterator多くのことを共有します。一方を他方にサブクラス化する必要がありますか?)。

脚注:Boostにはこれを緩和する機能があると確信していますが、多くの愚かな理由のため、ここでは使用できません。



GoFイテレーターパターンはまったく考慮されていますか?
DumbCoder 2010

3
@DumbCoder:C ++では、STLが提供するすべての既存のコンテナーとアルゴリズムでうまく機能するため、STL準拠のイテレーターを使用することが望ましい場合がよくあります。概念は似ていますが、GoFによって提案されたパターンにはいくつかの違いがあります。
ビョルンPollex


1
これらの回答の複雑さは、C ++がジャンプアップした学部生のための宿題以外には何の価値もない言語であるか、回答が複雑すぎて間違っていることを示唆しています。Cppにはもっと簡単な方法があるに違いありませんか?makeに関連する前のCMakeやAutomakeと同様に、Pythonプロトタイプからボイルアウトされたraw Cは、これよりもはるかに簡単に見えます。
クリストファー

回答:


156
  • コンテナに適合するイテレータのタイプを選択してください:入力、出力、転送など。
  • 標準ライブラリのベースイテレータクラスを使用します。たとえば、std::iteratorrandom_access_iterator_tag使用します。これらの基本クラスは、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標準にはなく、答えは可変から定数への変換を処理しません。たぶん継承したいと思うでしょうstd::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>
Yakov Galka

2
ええ、私はこれがどのように機能するのかよくわかりません。私がメソッドを持っている場合RefType operator*() { ... }、私は一歩近づきます-しかし、それでも必要なので、それは助けにはなりませんRefType operator*() const { ... }
Autumnsault 2013

31
std::iteratorC ++ 17で非推奨となるよう提案されています。
TypeIA 2016


5
これが非推奨の場合、代わりにそれを行う適切な「新しい」方法は何ですか?
SasQ

55

カスタムコンテナーのイテレーターを簡単に定義する方法を紹介しますが、念のため、任意のタイプのコンテナー、連続または不連続。

あなたはGithubでそれ見つけることができます

カスタムイテレータを作成して使用するための簡単な手順は次のとおりです。

  1. 「カスタムイテレータ」クラスを作成します。
  2. 「カスタムコンテナ」クラスで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-が加算しています。これは逆のようです
2017年

それはリバースイテレータ用で、operator +は後方に移動し、operator-は前方に移動する必要があります
Enzo

2
驚くばかり。受け入れられた答えは高すぎます。これは素晴らしいです。エンゾーありがとうございます。
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_iteratorsを修正するには、「次の要素へのポインタートリック」を使用する必要があります。
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 2010

ああ、それは有効ですが、コモーは警告を発し、他の多くの人も同様に警告するでしょう。enable_if修正するかもしれませんが、…
Potatoswatter 2010

一部のコンパイラは警告を出しますが(g ++が良き少年であることを警告しません)、コンパイラがとにかくそれを無効にするため、私はenable_ifを気にしませんでした。
Maxim Egorushkin、2010

1
@Matthieu:テンプレートコンストラクターを使用する場合、const_iteratorをイテレーターに変換すると、コンパイラーがコンストラクター内でエラーを生成し、ユーザーが混乱して頭を悩ませ、wtfを発声します。私が投稿した変換演算子を使用すると、コンパイラーはconst_iteratorからイテレーターへの適切な変換がないことを示すだけで、IMOの方が明確です。
Maxim Egorushkin

23

Boostには、Boost.Iteratorライブラリが役立ちます。

より正確には、このページ:boost :: iterator_adaptor

非常に興味深いのは カスタムタイプの完全な実装を最初から示すチュートリアルの例です。

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 2017

@kevinarpe:enabler呼び出し元がプロバイダーになることは決して意図されていないので、誤って渡そうとするのを防ぐために、プライベートにすることが考えられます。保護はにあるため、実際にはそれを通過することで問題が発生することはないと思いますenable_if
Matthieu M.

16

Boostに役立つものがあるかどうかはわかりません。

私が好むパターンは単純ですvalue_type。const修飾されているかどうかにかかわらず、に等しいテンプレート引数を取ります。必要に応じて、ノードタイプも指定します。その後、まあ、すべての種類のものが落ちます。

コピーコンストラクターやを含め、必要なすべてのものをパラメーター化(テンプレート化)することを忘れないでください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;

注:変換iterator-> const_iteratorとその逆が壊れているようです。
Maxim Egorushkin 2010

@Maxim:はい、実際に私のテクニック:vPの使用例を見つけることはできません。単に変換を示していないため、変換が壊れているという意味がcurわかりませんが、反対の定数のイテレータからのアクセスに問題がある可能性があります。頭に浮かぶ解決策はですがfriend my_container::const_iterator; friend my_container::iterator;、以前はそうではなかったと思います...とにかく、この一般的な概要は機能します。
Potatoswatter 2010

1
* friend classどちらの場合もそれを行います。
Potatoswatter 2010

しばらく経ちましたが、変換は(SFINAEによって)基になるメンバーの初期化の整形式に基づいて行われる必要があることを思い出します。これはSCARYパターンに従います(ただし、この投稿はその用語よりも古いものです)。
Potatoswatter 2013

12

良い答えはたくさんありますが、テンプレートヘッダーを作成しました使用は非常に簡潔で使いやすいものを作成しました。

イテレータをクラスに追加するには、イテレータの状態を表す小さなクラスを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.