如何正确实现自定义迭代器和const_iterators?


239

我有一个自定义容器类,我想为其编写iteratorconst_iterator类。

我以前从未做过,但是找不到合适的方法。关于迭代器创建的准则是什么,我应该注意什么?

我还想避免代码重复(我感觉到const_iteratoriterator共享许多东西;一个应该继承另一个吗?)。

脚注:我很确定Boost可以缓解此问题,但是由于许多愚蠢的原因,我不能在这里使用它。



是否完全考虑了GoF迭代器模式?
DumbCoder 2010年

3
@DumbCoder:在C ++中,通常需要具有兼容STL的迭代器,因为它们可以与STL提供的所有现有容器和算法很好地配合使用。尽管概念相似,但GoF提出的模式有所不同。
比约恩·波莱克斯(BjörnPollex)2010年


1
这些答案的复杂性表明,C ++要么是一门语言,要么除了上级本科生的家庭作业外,一无所有,或者答案过于复杂和错误。在Cpp中一定有更简单的方法吗?像CMake和Automake之前一样,从python原型中煮出来的原始C似乎比这容易得多。
克里斯托弗

Answers:


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这不在标准之内,答案无法处理可变的const转换。您可能想要继承,例如std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>
Yakov Galka

2
是的,我不太确定这是如何工作的。如果有了该方法RefType operator*() { ... },我将更近一步-但这没有帮助,因为我仍然需要RefType operator*() const { ... }
Autumnsault 2013年

31
std::iterator提议用于C ++ 17弃用
TypeIA

20
std::iterator 已弃用
diapir

5
如果不建议这样做,那么正确的“新”方法是什么?
SasQ

55

我将向您展示如何轻松地为自定义容器定义迭代器,但以防万一我创建了一个c ++ 11库,该库可让您轻松地针对任何类型的容器(相邻或连续)创建具有自定义行为的自定义迭代器非连续的。

你可以在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-应该向前移动
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为non- ,则它将无法编译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到迭代器的适当转换,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_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;

注意:看来您的转换迭代器-> const_iterator和返回已损坏。
Maxim Egorushkin

@Maxim:是的,我实际上找不到使用我的技术的任何示例:vP。我不确定您的意思是转换失败了,因为我只是没有说明它们,但是cur从相反的constness的迭代器访问可能会出现问题。friend my_container::const_iterator; friend my_container::iterator;我想到的解决方案是,但是我不认为这是我以前做过的……总之,总的说来可行。
Potatoswatter

1
* friend class在两种情况下都要这样做。
Potatoswatter

已经有一段时间了,但是我现在想起来,转换(应该由SFINAE进行)是基于基础成员初始化的格式正确的。这遵循SCARY模式(但本文早于该术语)。
Potatoswatter

12

有很多好的答案,但是我创建了一个模板头,非常简洁且易于使用。

要将迭代器添加到您的类,只需编写一个小类来表示具有7个小函数的迭代器状态,其中两个小函数是可选的:

#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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.