【问题标题】:How to correctly implement custom iterators and const_iterators?如何正确实现自定义迭代器和 const_iterators?
【发布时间】:2011-04-04 16:48:29
【问题描述】:

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

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

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

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

【问题讨论】:

  • @DumbCoder:在 C++ 中,通常希望有符合 STL 的迭代器,因为它们可以很好地与 STL 提供的所有现有容器和算法一起工作。虽然概念相似,但与 GoF 提出的模式存在一些差异。
  • 这些答案的复杂性表明,C++ 要么是一门不值得升读本科生家庭作业的语言,要么答案过于复杂和错误。 Cpp中必须有更简单的方法吗?就像之前的 CMake 和 Automake 相对于 make 一样,从 python 原型中提取的原始 C 似乎比这容易得多。
  • @Chris C++ 当然值得。 C++ 的复杂性和学习曲线是对其极其优化的抽象的相对独特属性的一种支付。有人说零成本抽象,在许多情况下,这正是现代 C++ 中发生的事情。
  • @PavelŠimerda 哈哈是的,我完全不同意我在这一点上的评论。我想我更多的是按照“必须有更简单的方法”来思考

标签: c++ iterator const-iterator


【解决方案1】:
  • 选择适合您容器的迭代器类型:输入、输出、转发等。
  • 使用标准库中的基迭代器类。例如,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 迭代器的类型。

另请参阅:standard library reference

编辑: std::iterator 自 C++17 起已弃用。查看相关讨论here

【讨论】:

  • @Potatoswatter:没有对此投反对票,但是,嘿,random_access_iterator 不在标准中,答案不处理可变到 const 的转换。你可能想继承,例如std::iterator&lt;random_access_iterator_tag, value_type, ... optional arguments ...&gt; 不过。
  • 是的,我不太确定它是如何工作的。如果我有方法RefType operator*() { ... },我就更近了一步——但这并没有帮助,因为我仍然需要RefType operator*() const { ... }
  • std::iterator has been deprecated
  • 如果不推荐使用,那么正确的“新”方法是什么?
  • @SasQ 您只需自己定义成员类型(这不是额外的工作) - 如果您喜欢,也可以创建一个与 std::iterator 类似的模板。
【解决方案2】:

我将向您展示如何轻松地为您的自定义容器定义迭代器,但以防万一我创建了一个 c++11 库,它允许您轻松地为任何类型的容器创建具有自定义行为的自定义迭代器, 连续的或不连续的。

你可以找到它on Github

以下是创建和使用自定义迭代器的简单步骤:

  1. 创建您的“自定义迭代器”类。
  2. 在“自定义容器”类中定义 typedef。
    • 例如typedef blRawIterator&lt; Type &gt; iterator;
    • 例如typedef blRawIterator&lt; const Type &gt; const_iterator;
  3. 定义“开始”和“结束”函数
    • 例如iterator begin(){return iterator(&amp;m_data[0]);};
    • 例如const_iterator cbegin()const{return const_iterator(&amp;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- 应该前进
  • 太棒了。接受的答案太高了。这太棒了。谢谢恩佐。
  • 您需要编辑您的答案。假设 m_data 分配有 m_size 元素,您会得到未定义的行为:m_data[m_size] 是 UB。您可以通过将其替换为 m_data+m_size 来简单地修复它。对于反向迭代器,m_data[-1]m_data-1 都不正确 (UB)。要修复 reverse_iterators,您需要使用“指向下一个元素技巧的指针”。
  • 反向迭代器是徒劳的,因为标准库提供了一个反向迭代器适配器。而且您未能使迭代器类型可以从 const 迭代器中分配。
【解决方案3】:

他们经常忘记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&lt;T&gt;如何转换为IntrusiveSlistIterator&lt;T const&gt;。如果T 已经是const,则永远不会使用此转换。

【讨论】:

  • 其实,你也可以反过来定义一个拷贝构造函数,它是模板,如果你尝试将底层类型从const转换为非@987654329,它将无法编译@.
  • 你不会得到一个无效的IntrusiveSlistIterator&lt;T const, void&gt;::operator IntrusiveSlistIterator&lt;T const, void&gt;() const吗?
  • 啊,它是有效的,但是 Comeau 给出了一个警告,我怀疑很多其他人也会这样做。 enable_if 可能会修复它,但是……
  • 我没有理会 enable_if 因为编译器无论如何都会禁用它,尽管有些编译器会发出警告(g++ 是个好孩子不会发出警告)。
  • @Matthieu:如果使用模板构造函数,当将 const_iterator 转换为迭代器时,编译器会在构造函数内部产生错误,让用户在困惑中挠头,说不出话来。使用我发布的转换运算符,编译器只是说没有合适的从 const_iterator 到迭代器的转换,IMO 更清楚。
【解决方案4】:

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: enabler 绝不打算由调用者提供,所以我猜他们将其设为私有是为了避免人们不小心试图通过它。我不认为,实际上通过它可能会产生任何问题,因为保护在于enable_if
  • @orenrevenge:这是从链接复制/粘贴的,包括格式。欢迎使用 Boost 代码...
【解决方案5】:

我不知道 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 和 back 都坏了。
  • @Maxim:是的,我实际上找不到任何使用我的技术的例子 :vP 。我不确定你的意思是转换被破坏了,因为我只是没有说明它们,但可能存在从相反常量的迭代器访问cur 的问题。想到的解决方案是friend my_container::const_iterator; friend my_container::iterator;,但我不认为我以前是这样做的……无论如何,这个总体大纲是有效的。
  • * 在这两种情况下都使用friend class
  • 已经有一段时间了,但我现在记得转换应该(由 SFINAE)基于基础成员初始化的格式良好。这遵循 SCARY 模式(但这篇文章早于该术语)。
【解决方案6】:

有很多好的答案,但我创建了一个template header,我使用它非常简洁且易于使用。

要为你的类添加一个迭代器,只需要编写一个小类来表示迭代器的状态,其中包含 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
  }
}

希望对你有帮助。

【讨论】:

    【解决方案7】:

    我看到了这篇文章,很惊讶这里没有真正提到一个简单的方法。使用指向像std::iterator 描述的值的指针显然是一种非常通用的方法。但是你也许可以摆脱一些更简单的事情。当然,这是一种简单的方法,可能并不总是足够的,但如果是的话,我会将其发布给下一位读者。

    你的类中的底层类型很可能是一个 STL 容器,它已经为你定义了迭代器。如果是这种情况,您可以简单地使用他们定义的迭代器,而无需自己制作。

    这是一个例子:

    class Foo {
    
      std::vector<int>::iterator begin() { return data.begin(); }
      std::vector<int>::iterator end() { return data.end(); }
    
      std::vector<int>::const_iterator begin() const { return data.begin(); }
      std::vector<int>::const_iterator end() const { return data.end(); }
    
    
    private:
      std::vector<int> data
    
    };
    

    【讨论】:

      【解决方案8】:

      我很想知道这是如何正确,但似乎可以作为内部数据存储的滚动你自己的迭代器

      template<typename T>
      struct iterator_type
      {
          using self_type             = iterator_type;
          using iterator_category     = std::random_access_iterator_tag;
          using difference_type       = std::ptrdiff_t;
          using value_type            = std::remove_cv_t<T>;
          using pointer               = T*;
          using reference             = T&;
      
          iterator_type( pointer ptr ) noexcept
              : _ptr{ ptr }
          {}
      
          reference operator*() noexcept { return *_ptr; }
          pointer operator->() noexcept { return _ptr; }
      
          self_type operator++() noexcept { ++_ptr; return *this; }
          self_type operator++(int) noexcept { self_type tmp = *this; ++_ptr; return tmp; }
      
          self_type operator--() noexcept { --_ptr; return *this; }
          self_type operator--(int) noexcept { self_type tmp = *this; --_ptr; return tmp; }
      
          bool operator==( const self_type &other ) const noexcept { return _ptr == other._ptr; }
          bool operator!=( const self_type &other ) const noexcept { return _ptr != other._ptr; }
      
      private:
          pointer _ptr;
      };
      
      
      template<typename T>
      using const_iterator_type = iterator_type<std::add_const_t<T>>;
      

      然后我只是将这些添加到我的课程中,并且似乎按预期工作。

      template<typename T>
      class Container
      {
      public:
          using iterator               = iterator_type<T>;
          using const_iterator         = const_iterator_type<T>;
          using reverse_iterator       = std::reverse_iterator<iterator>;
          using const_reverse_iterator = std::reverse_iterator<const_iterator>;
      
      ...
      
          iterator begin() { return _begin; }
          iterator end() { return _begin + _size; }
      
          const_iterator cbegin() const { return _begin; }
          const_iterator cend() const { return _begin + _size; }
      
          reverse_iterator rbegin() { return reverse_iterator(_begin + _size); }
          reverse_iterator rend() { return reverse_iterator(_begin); }
      
          const_reverse_iterator crbegin() const { return const_reverse_iterator(_begin + _size); }
          const_reverse_iterator crend() const { return const_reverse_iterator(_begin); }
      
      private:
          T*         _begin;
          size_t     _size;
          size_t     _capacity;
      };
      

      唯一的事情是,要使用 std::cbegin()std::rcbegin()std::cend()std::rcend() 函数,我必须扩展 std 命名空间:

      namespace std
      {
          template<typename T>
          typename Container<T>::const_iterator cbegin( Container<T> &c ) { return c.cbegin(); }
      
          template<typename T>
          typename Container<T>::const_iterator cend( Container<T> &c ) { return c.cend(); }
      
          template<typename T>
          typename Container<T>::const_reverse_iterator crbegin( Container<T> &c ) { return c.crbegin(); }
      
          template<typename T>
          typename Container<T>::const_reverse_iterator crend( Container<T> &c ) { return c.crend(); }
      }
      

      【讨论】:

        【解决方案9】:

        检查下面的代码,它可以工作

        #define MAX_BYTE_RANGE 255
        
        template <typename T>
        class string
        {
        public:
            typedef char *pointer;
            typedef const char *const_pointer;
            typedef __gnu_cxx::__normal_iterator<pointer, string> iterator;
            typedef __gnu_cxx::__normal_iterator<const_pointer, string> const_iterator;
        
            string() : length(0)
            {
            }
            size_t size() const
            {
                return length;
            }
            void operator=(const_pointer value)
            {
                if (value == nullptr)
                    throw std::invalid_argument("value cannot be null");
                auto count = strlen(value);
                if (count > 0)
                    _M_copy(value, count);
            }
            void operator=(const string &value)
            {
                if (value.length != 0)
                    _M_copy(value.buf, value.length);
            }
            iterator begin()
            {
                return iterator(buf);
            }
            iterator end()
            {
                return iterator(buf + length);
            }
            const_iterator begin() const
            {
                return const_iterator(buf);
            }
            const_iterator end() const
            {
                return const_iterator(buf + length);
            }
            const_pointer c_str() const
            {
                return buf;
            }
            ~string()
            {
            }
        
        private:
            unsigned char length;
            T buf[MAX_BYTE_RANGE];
        
            void _M_copy(const_pointer value, size_t count)
            {
                memcpy(buf, value, count);
                length = count;
            }
        };
        

        【讨论】:

          猜你喜欢
          • 2017-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-23
          • 2018-08-25
          • 2015-05-26
          • 1970-01-01
          • 2016-01-10
          相关资源
          最近更新 更多