Làm thế nào để thực hiện chính xác các trình lặp và const_iterators tùy chỉnh?


240

Tôi có một lớp container tùy chỉnh mà tôi muốn viết iteratorconst_iteratorcác lớp.

Tôi chưa bao giờ làm điều này trước đây và tôi đã thất bại trong việc tìm ra cách làm phù hợp. Các hướng dẫn liên quan đến việc tạo iterator là gì và tôi cần lưu ý điều gì?

Tôi cũng muốn tránh việc sao chép mã (tôi cảm thấy điều đó const_iteratoriteratorchia sẻ nhiều thứ; có nên phân lớp thứ khác không?).

Lưu ý chân: Tôi khá chắc chắn Boost có thứ gì đó để giảm bớt điều này nhưng tôi không thể sử dụng nó ở đây, vì nhiều lý do ngu ngốc.



Mô hình lặp của GoF có được xem xét không?
DumbCoder

3
@DumbCoder: Trong C ++, người ta thường mong muốn có các trình vòng lặp tuân thủ STL, vì chúng sẽ hoạt động tốt với tất cả các vùng chứa và thuật toán hiện có do STL cung cấp. Mặc dù khái niệm này tương tự nhau, nhưng có một số khác biệt đối với mô hình do GoF đề xuất.
Bjorn Pollex

Tôi đã đăng mẫu trình lặp tùy chỉnh tại đây
Valdemar_Rudolfovich

1
Sự phức tạp của những câu trả lời này cho thấy C ++ là ngôn ngữ không xứng đáng với bất kỳ thứ gì khác ngoài bài tập về nhà cho những sinh viên chưa tốt nghiệp, hoặc câu trả lời bị sai và quá sai. Có phải là một cách dễ dàng hơn trong Cpp? Giống như CMake và Automake trước khi tạo ra, C thô được tạo ra từ một nguyên mẫu python có vẻ dễ dàng hơn nhiều so với điều này.
Christopher

Câu trả lời:


156
  • Chọn loại trình vòng lặp phù hợp với vùng chứa của bạn: đầu vào, đầu ra, chuyển tiếp, v.v.
  • Sử dụng các lớp lặp cơ sở từ thư viện tiêu chuẩn. Ví dụ, std::iteratorvới random_access_iterator_tagcác lớp cơ sở .These xác định tất cả các định nghĩa kiểu được yêu cầu bởi STL và thực hiện các công việc khác.
  • Để tránh trùng lặp mã lớp iterator phải là một lớp mẫu và được tham số hóa bởi "loại giá trị", "loại con trỏ", "loại tham chiếu" hoặc tất cả chúng (phụ thuộc vào việc thực hiện). Ví dụ:

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

    Thông báo iterator_typeconst_iterator_typeđịnh nghĩa kiểu: chúng là các kiểu cho các trình lặp không phải const và const của bạn.

Xem thêm: tham khảo thư viện tiêu chuẩn

EDIT: std::iterator không dùng nữa kể từ C ++ 17. Xem một cuộc thảo luận liên quan ở đây .


8
@Potatoswatter: Chưa đánh giá thấp điều này, nhưng, hey, random_access_iteratorkhông có trong tiêu chuẩn và câu trả lời không xử lý việc chuyển đổi thành const. Bạn có thể muốn thừa kế từ, ví dụ std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>.
Yakov Galka

2
Vâng, tôi không chắc chắn làm thế nào điều này hoạt động. Nếu tôi có phương pháp RefType operator*() { ... }, tôi sẽ tiến một bước gần hơn - nhưng nó không giúp được gì, vì tôi vẫn cần RefType operator*() const { ... }.
Autumnsault


20
std::iterator đã bị từ
chối

5
Nếu điều này không được chấp nhận, cách thay thế "mới" thích hợp để làm điều đó là gì?
SasQ

55

Tôi sẽ chỉ cho bạn cách bạn có thể dễ dàng xác định các trình vòng lặp cho các thùng chứa tùy chỉnh của mình, nhưng chỉ trong trường hợp tôi đã tạo thư viện c ++ 11 cho phép bạn dễ dàng tạo các trình vòng lặp tùy chỉnh với hành vi tùy chỉnh cho bất kỳ loại vùng chứa nào, liền kề hoặc không tiếp giáp.

Bạn có thể tìm thấy nó trên Github

Dưới đây là các bước đơn giản để tạo và sử dụng các trình vòng lặp tùy chỉnh:

  1. Tạo lớp "iterator tùy chỉnh" của bạn.
  2. Xác định typedefs trong lớp "thùng chứa tùy chỉnh" của bạn.
    • ví dụ typedef blRawIterator< Type > iterator;
    • ví dụ typedef blRawIterator< const Type > const_iterator;
  3. Xác định chức năng "bắt đầu" và "kết thúc"
    • ví dụ iterator begin(){return iterator(&m_data[0]);};
    • ví dụ const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Đã được thực hiện!!!

Cuối cùng, vào việc xác định các lớp lặp tùy chỉnh của chúng tôi:

LƯU Ý: Khi xác định các trình vòng lặp tùy chỉnh, chúng tôi xuất phát từ các loại trình vòng lặp tiêu chuẩn để cho các thuật toán STL biết loại trình vòng lặp mà chúng tôi đã thực hiện.

Trong ví dụ này, tôi định nghĩa một trình vòng lặp truy cập ngẫu nhiên và một trình vòng lặp truy cập ngẫu nhiên ngược:

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

Bây giờ ở đâu đó trong lớp container tùy chỉnh của bạn:

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

Tôi nghĩ toán tử + và toán tử- có thể có các thao tác ngược. Có vẻ như toán tử + đang trừ chuyển động từ con trỏ không thêm và toán tử- đang thêm nó. Điều này có vẻ ngược
Bãi biển

Đó là cho trình vòng lặp ngược, toán tử + nên đi lùi và toán tử- nên đi tiếp
Enzo

2
Tuyệt vời. Câu trả lời được chấp nhận là mức độ quá cao. Điều này thật tuyệt. Cảm ơn Enzo.
FernandoZ

Bạn cần chỉnh sửa câu trả lời của bạn. Giả sử m_data được phân bổ với các phần tử m_size bạn nhận được Hành vi không xác định: m_data[m_size]là UB. Bạn có thể chỉ cần sửa nó bằng cách thay thế nó bằng m_data+m_size. Đối với các trình vòng lặp ngược, cả hai m_data[-1]m_data-1không chính xác (UB). Để sửa lỗi Reverse_iterators, bạn sẽ cần sử dụng "con trỏ tới thủ thuật phần tử tiếp theo".
Arnaud

Arnaud, tôi vừa thêm thành viên con trỏ vào lớp container tùy chỉnh để hiển thị tốt hơn những gì tôi muốn nói.
Enzo

24

Họ thường quên rằng iteratorphải chuyển đổi sang const_iteratornhưng không phải là cách khác. Đây là một cách để làm điều đó:

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

Trong thông báo trên làm thế nào IntrusiveSlistIterator<T>để chuyển đổi thành IntrusiveSlistIterator<T const>. Nếu Tđã constchuyển đổi này không bao giờ được sử dụng.


Trên thực tế, bạn cũng có thể thực hiện theo cách khác bằng cách xác định một hàm tạo sao chép là mẫu, nó sẽ không biên dịch nếu bạn cố gắng chuyển loại cơ bản từ constsang không const.
Matthieu M.

Bạn sẽ không có một không hợp lệ IntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() const?
Potatoswatter

Ah, nó hợp lệ, nhưng Comeau đưa ra cảnh báo và tôi nghi ngờ rất nhiều người khác cũng sẽ như vậy. An enable_ifcó thể sửa nó, nhưng 18
Potatoswatter

Tôi không bận tâm với enable_if vì trình biên dịch sẽ vô hiệu hóa nó, mặc dù một số trình biên dịch đưa ra cảnh báo (g ++ là một cậu bé tốt không cảnh báo).
Maxim Egorushkin

1
@Matthieu: Nếu một người đi với một hàm tạo mẫu, khi chuyển đổi const_iterator thành iterator, trình biên dịch sẽ tạo ra một lỗi bên trong hàm tạo, làm cho người dùng gãi đầu bối rối và thốt ra wtf. Với toán tử chuyển đổi tôi đã đăng, trình biên dịch chỉ nói rằng không có chuyển đổi phù hợp từ const_iterator sang iterator, mà IMO, rõ ràng hơn.
Maxim Egorushkin

23

Boost có một cái gì đó để giúp: thư viện Boost.Iterator.

Chính xác hơn là trang này: boost :: iterator_adaptor .

Điều thú vị là Ví dụ Hướng dẫn cho thấy việc triển khai hoàn chỉnh, từ đầu, cho một loại tùy chỉnh.

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

Điểm chính, như đã được trích dẫn, là sử dụng một triển khai mẫu duy nhất và typedefnó.


Bạn có thể giải thích ý nghĩa của bình luận này? // a private type avoids misuse
kevinarpe

@kevinarpe: enablerkhông bao giờ có ý định trở thành nhà cung cấp bởi người gọi, vì vậy tôi đoán là họ làm cho nó riêng tư để tránh mọi người vô tình cố gắng vượt qua nó. Tôi không nghĩ rằng, nó có thể tạo ra bất kỳ vấn đề nào để vượt qua nó, vì sự bảo vệ nằm ở đó enable_if.
Matthieu M.

16

Tôi không biết Boost có gì giúp được không.

Mẫu ưa thích của tôi rất đơn giản: lấy một đối số mẫu bằng value_type, có thể đủ điều kiện hoặc không. Nếu cần thiết, cũng là một loại nút. Sau đó, tốt, tất cả mọi thứ rơi vào vị trí.

Chỉ cần nhớ tham số hóa (mẫu-ize) mọi thứ cần phải có, bao gồm cả hàm tạo sao chép và operator==. Đối với hầu hết các phần, ngữ nghĩa của constsẽ tạo ra hành vi chính xác.

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;

Lưu ý: có vẻ như iterator chuyển đổi của bạn-> const_iterator và trở lại bị hỏng.
Maxim Egorushkin

@Maxim: Có, tôi thực sự không thể tìm thấy bất kỳ ví dụ nào về việc sử dụng kỹ thuật của mình: vP. Tôi không chắc ý của bạn là các chuyển đổi bị hỏng, vì đơn giản là tôi không minh họa chúng, nhưng có thể có một vấn đề truy cập curtừ trình lặp của hằng số ngược lại. Giải pháp xuất hiện trong đầu là friend my_container::const_iterator; friend my_container::iterator;, nhưng tôi không nghĩ đó là cách tôi đã làm trước khi dù sao thì phác thảo chung này hoạt động.
Potatoswatter

1
* làm điều đó friend classtrong cả hai trường hợp.
Potatoswatter

Đã có lúc, nhưng tôi nhớ lại rằng các chuyển đổi nên được cung cấp (bởi SFINAE) về sự hình thành tốt của các khởi tạo thành viên cơ bản. Điều này tuân theo mô hình SCary (nhưng bài đăng này có trước thuật ngữ đó).
Potatoswatter

12

Có rất nhiều câu trả lời hay nhưng tôi đã tạo một tiêu đề mẫu tôi sử dụng khá ngắn gọn và dễ sử dụng.

Để thêm một trình vòng lặp vào lớp của bạn, chỉ cần viết một lớp nhỏ để thể hiện trạng thái của trình vòng lặp với 7 hàm nhỏ, trong đó có 2 tùy chọn:

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

Sau đó, bạn có thể sử dụng nó như bạn mong đợi từ một trình lặp 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
  }
}

Tôi hy vọng nó sẽ giúp.


1
Tệp mẫu này đã giải quyết tất cả các vấn đề lặp của tôi!
Perrykipkerrie
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.